【问题标题】:Advantages of createElement over innerHTML?createElement 相对于 innerHTML 的优势?
【发布时间】:2011-02-26 03:52:33
【问题描述】:

在实践中,使用 createElement 比使用 innerHTML 有什么优势?我之所以问,是因为我确信使用 innerHTML 在性能和代码可读性/可维护性方面更有效,但我的队友已经决定使用 createElement 作为编码方法。我只是想了解 createElement 如何更高效。

【问题讨论】:

标签: javascript dom innerhtml createelement


【解决方案1】:

除了安全性之外,使用createElement 而不是修改innerHTML(而不是仅仅丢弃已经存在的内容并替换它)还有几个优点,就像 Pekka 已经提到的那样:

在追加元素时保留对 DOM 元素的现有引用

当您追加(或以其他方式修改)innerHTML 时,该元素内的所有 DOM 节点都必须重新解析和重新创建。如果您保存了对节点的任何引用,它们将基本上是无用的,因为它们不再显示了。

保留附加到任何 DOM 元素的事件处理程序

这实际上只是最后一种情况的特例(尽管很常见)。设置innerHTML 不会自动将事件处理程序重新附加到它创建的新元素,因此您必须自己跟踪它们并手动添加它们。在某些情况下,事件委托可以消除这个问题。

在某些情况下可能更简单/更快

如果你做了很多添加,你肯定不想一直重置innerHTML,因为虽然简单的更改更快,但重复重新解析和创建元素会更慢。解决这个问题的方法是在一个字符串中构建 HTML,并在完成后设置一次innerHTML。根据具体情况,字符串操作可能比创建元素并附加它们要慢。

此外,字符串操作代码可能更复杂(特别是如果您希望它是安全的)。

这是我有时使用的一个函数,它使createElement 的使用更加方便。

function isArray(a) {
    return Object.prototype.toString.call(a) === "[object Array]";
}

function make(desc) {
    if (!isArray(desc)) {
        return make.call(this, Array.prototype.slice.call(arguments));
    }

    var name = desc[0];
    var attributes = desc[1];

    var el = document.createElement(name);

    var start = 1;
    if (typeof attributes === "object" && attributes !== null && !isArray(attributes)) {
        for (var attr in attributes) {
            el[attr] = attributes[attr];
        }
        start = 2;
    }

    for (var i = start; i < desc.length; i++) {
        if (isArray(desc[i])) {
            el.appendChild(make(desc[i]));
        }
        else {
            el.appendChild(document.createTextNode(desc[i]));
        }
    }

    return el;
}

如果你这样称呼它:

make(["p", "Here is a ", ["a", { href:"http://www.google.com/" }, "link"], "."]);

你会得到这个 HTML 的等价物:

<p>Here is a <a href="http://www.google.com/">link</a>.</p>

【讨论】:

  • 单独创建一个 DOM 树片段,然后在最后将其附加到真实的 DOM 上,这也证明了速度优势。
  • @staticsan,这是一个很好的观点(make 函数可以让另一件事变得更容易一些)。
  • 单独创建一个 DOM 片段肯定会加快插入速度。关于 innerHTML,也可以/应该使用相同的方法。我通常将我的 HTML 字符串推送到一个数组,然后使用内置的数组连接函数(以我的经验最快)连接元素,然后将其附加到 DOM。
  • @Matthew Crumley:IYO,你认为你上面提到的优点(这些都是有效的)超过了在实际使用中使用innerHTML的优点吗?更具体地说,假设您正在编写代码以动态构建表格(一个非常常见的编码任务),您会为这两种方法使用 createElement 还是 innerHTML 构建片段。
  • 我通常避免使用innerHTML,因为它有缺点。对于复杂的标记(比如建表),我通常会编写函数来生成标记的每个部分。例如,我将有一个函数从每一行的数据中生成一个tr。然后我可能有另一个函数将行组合成一个表。每个函数都可以像使用适当的参数调用make 一样简单。如果性能成为问题,我可以更改函数以返回 HTML 字符串。
【解决方案2】:

虽然innerHTML 可能更快,但我不同意它在可读性或维护方面更好。将所有内容放在一个字符串中可能会更短,但更短的代码不一定更易于维护。

当需要将动态 DOM 元素创建为加号时,字符串连接无法扩展,并且引号的开头和结尾变得难以跟踪。考虑以下示例:

生成的元素是一个具有两个内部跨度的 div,其内容是动态的。第一个跨度中的一个类名(战士)也是动态的。

<div>
    <span class="person warrior">John Doe</span>
    <span class="time">30th May, 2010</span>
</div>

假设已经定义了以下变量:

var personClass = 'warrior';
var personName = 'John Doe';
var date = '30th May, 2010';

仅使用 innerHTML 并将所有内容混合成一个字符串,我们得到:

someElement.innerHTML = "<div><span class='person " + personClass + "'>" + personName + "</span><span class='time'>" + date + "</span></div>";

上面的混乱可以通过使用字符串替换来清理,以避免每次打开和关闭字符串。即使是简单的文本替换,我更喜欢使用replace 而不是字符串连接。

这是一个简单的函数,它接受键和替换值的对象并在字符串中替换它们。它假设键以$ 为前缀,表示它们是一个特殊值。它不会进行任何转义或处理 $ 出现在替换值等中的边缘情况。

function replaceAll(string, map) {
    for(key in map) {
        string = string.replace("$" + key, map[key]);
    }
    return string;
}

var string = '<div><span class="person $type">$name</span><span class="time">$date</span></div>';
var html = replaceAll(string, {
    type: personClass,
    name: personName,
    date: date
});
someElement.innerHTML = html;

​这可以通过在构造对象时分离属性、文本等来改进,以获得对元素构造的更多编程控制。例如,使用 MooTools,我们可以将对象属性作为地图传递。这当然更易于维护,我也认为更具可读性。 jQuery 1.4 使用类似的语法来传递一个映射来初始化 DOM 对象。

var div = new Element('div');

var person = new Element('span', {
    'class': 'person ' + personClass,
    'text': personName
});

var when =  new Element('span', {
    'class': 'time',
    'text': date
});

div.adopt([person, when]);

我不会将下面的纯 DOM 方法称为比上面的更具可读性,但它肯定更易于维护,因为我们不必跟踪开/关引号和众多加号。

var div = document.createElement('div');

var person = document.createElement('span');
person.className = 'person ' + personClass;
person.appendChild(document.createTextNode(personName));

var when = document.createElement('span');
​when.className = 'date​​​​​​';
when.appendChild(document.createTextNode(date));

​div.appendChild(person);
div.appendChild(when);

最易读的版本很可能来自使用某种JavaScript templating

<div id="personTemplate">
    <span class="person <%= type %>"><%= name %></span>
    <span class="time"><%= date %></span>
</div>

var div = $("#personTemplate").create({
    name: personName,
    type: personClass,
    date: date
});

【讨论】:

  • @Anurag:对于您关于innerHTML 的第一个示例,我倾向于这样写。我知道它有点长,但就我个人而言,它对我来说是可读的,甚至片段的结构也得到了维护。对此有何看法? array.push("
    "); array.push("", personName, ""); array.push("", date, "); array.push("
    "); someElement.innerHTML = array.join("" );
  • 把代码放在反引号var a = hello 中,但我可以找出你的代码。这看起来比单个串联字符串更具可读性。
  • 此外,即使分解为多个步骤,处理标签/属性的打开和关闭仍然很烦人。相反,字符串替换更简洁,更防错。我在回答中添加了上面的示例。它可能会错过 IE 所需的字符串构造微优化,但除非你真的需要,否则你不应该为一个浏览器复杂化你的代码。
  • 我认为这个例子不足以证明任何事情。您需要使用 innerHTML 和手动构建来创建不同大小和复杂性(深度)的 DOM,如果结果一致,那将清楚地表明 innerHTML 速度较慢。此外,该测试高度倾斜,因为它强制渲染引擎使 body 标签的整个 DOM 无效,并重建它。而在另一个例子中它只是将一个新节点附加到 DOM 中,并没有触及 body 标签的其他部分。
【解决方案3】:

用户bobince 在他的critique of jQuery 中非常非常好地提出了一些缺点。

...另外,您可以通过说 $(''+message+'') 来创建一个 div,而不必乱用 document.createElement('div') 和文本节点。万岁!只是……等等。您还没有逃脱该 HTML,并且可能刚刚创建了一个跨站点脚本安全漏洞,这次只是在客户端。在您花了很长时间清理 PHP 以在服务器端使用 htmlspecialchars 之后。多可惜。嗯,没有人真正关心正确性或安全性,不是吗?

jQuery 不能完全为此负责。毕竟,innerHTML 属性已经存在多年,并且已经证明比 DOM 更受欢迎。但该库确实鼓励这种编码风格。

至于性能:InnerHTML 肯定会更慢,因为它需要被解析并在内部转换为 DOM 元素(可能使用createElement 方法)。

根据@Pointy 提供的quirksmode benchmark,InnerHTML 在所有浏览器中都更快。

至于可读性和易用性,在大多数项目中,您会发现我在一周中的任何一天都选择innerHTML 而不是createElement。但正如你所见,createElement 有很多观点。

【讨论】:

  • 呃@Pekka 你确定innerHTML 会变慢吗?我知道很长一段时间以来这绝对是错误的。使用innerHTML 实际上在框架内部变得流行,正是因为在某些浏览器中具有显着的性能优势(猜猜看)。
  • @Pointy 有趣。我没有基准,但常识告诉我 innerHTML 必须 更慢:它必须被解析、验证并转换为 DOM 元素,这是createElement 在第一步中所做的。不过,如果您知道任何基准测试另有说明,我会很高兴地予以纠正。
  • 好吧,quirksmode 基准测试有点老了:quirksmode.org/dom/innerhtml.html
  • @Pointy 仍然:哇!完全违背了我的想法。干杯,将更新我的答案。
  • 感谢 Pekka 和 Pointy 的洞察力。这强化了我的观点,即 innerHTML 更快(除了编码经验)。此外,在数组中使用 html 字符串还提供了额外的性能增益和可读性。我想知道的是 createElement 是否有任何使用它的意义。
【解决方案4】:

如果您想在代码中保留引用,则应使用 createElement。 InnerHTML 有时会产生难以发现的错误。

HTML 代码:

<p id="parent">sample <span id='test'>text</span> about anything</p>

JS代码:

var test = document.getElementById("test");

test.style.color = "red"; //1 - it works

document.getElementById("parent").innerHTML += "whatever";

test.style.color = "green"; //2 - oooops

1) 你可以改变颜色

2) 您不能再更改颜色或其他任何内容,因为在上面的行中,您向 innerHTML 添加了一些内容,并且所有内容都被重新创建,并且您可以访问不再存在的内容。为了改变它,你必须再次getElementById

您需要记住,它也会影响任何事件。您需要重新应用事件。

InnerHTML 很棒,因为它更快,而且大多数时候更容易阅读,但你必须小心并谨慎使用它。如果你知道你在做什么,你会没事的。

【讨论】:

    【解决方案5】:

    模板文字(模板字符串)是另一种选择。

    const container = document.getElementById("container");
    
    const item_value = "some Value";
    
    const item = `<div>${item_value}</div>`
    
    container.innerHTML = item;
    

    【讨论】:

    • 使用此方法时要注意HTML注入。
    猜你喜欢
    • 2017-03-19
    • 2010-12-23
    • 2015-07-23
    • 1970-01-01
    • 2012-05-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多