createElement优于innerHTML的优势?


Answers:


77

除了安全之外,使用其他方法还具有一些优点,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>

5
单独创建DOM树片段,然后将其一次性附加到真实DOM上,还显示了速度优势。
staticsan

@staticsan,这是个好主意(make函数可以使事情变得更简单)。
马修·克鲁姆利

孤立地创建DOM片段肯定会加快插入速度。关于innerHTML,也可以/应该使用相同的方法。我通常将我的HTML字符串推到一个数组中,然后使用内置的数组连接功能(以我的经验最快)将元素连接在一起,然后将其附加到DOM。
oninea

@Matthew Crumley:IYO,您是否认为您上面提到的优点(全部被证明)超过了在实际使用中使用innerHTML的优点?更具体地说,假设您正在编写代码以动态构建表(这是一个非常常见的编码任务),而您将两种方法都使用createElement还是innerHTML构建片段。
oninea 2010年

1
innerHTML由于其缺点,我通常会避免使用。对于复杂的标记(如构建表),我通常编写函数来生成标记的每个部分。例如,我将有一个函数tr根据每一行的数据生成一个。然后,我可能会有另一个将行合并到表中的函数。每个函数都可以像make使用适当参数进行调用一样简单。如果性能成为问题,我可以更改函数以返回HTML字符串。
马修·克鲁姆利

14

虽然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模板生成的

<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(“ <div>”); array.push(“ <span class ='”,person + personClass,“'>”,personName,“ </ span>”); array.push(“ <span class =”',time,“'>”,date,</ span>“); array.push(” </ div>“); someElement.innerHTML = array.join(”“ );
oninea

将代码放在反引号中var a = hello,但我可以找出您的代码。这看起来比单个连接字符串更具可读性。
阿努拉格

而且,即使分解成各个步骤,处理标签/属性的开头和结尾仍然很烦人。取而代之的是,字符串替换更干净,更防错。我在答案中添加了一个示例。它可能会错过IE所需的字符串构造微优化,但是除非您确实需要,否则您不应使一个浏览器的代码复杂化。
阿努拉格


我认为该示例的结论性不足以证明任何事情。您需要使用innerHTML和手动构造来创建大小和复杂度(深度)不同的DOM,如果结果一致,则很显然这表明innerHTML速度较慢。同样,该测试存在很大的偏差,因为它迫使渲染引擎使body标签的整个DOM无效并对其进行重构。尽管在另一个示例中,它仅将新节点追加到DOM,但不涉及body标签的其他部分。
阿努拉格2012年

14

用户bobince对jQuery的评论中非常非常好地提出了许多缺点。

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

jQuery并不能完全怪罪于此。毕竟,innerHTML属性已经存在了很多年,并且已经证明比DOM更流行。但是库肯定会鼓励这种编码风格。

至于性能:InnerHTML肯定会变慢,因为需要对其进行解析并将其内部转换为DOM元素(可能使用该createElement方法)。

根据@Pointy提供的quirksmode基准,InnerHTML在所有浏览器中的运行速度都更快。

至于可读性和易用性,你会发现我选择innerHTMLcreateElement一周的任何一天,大多数项目。但是正如您所看到的,有很多要说的要点createElement


1
恩@Pekka您确定innerHTML要慢一点吗?我知道很长时间以来,这绝对是错误的。innerHTML实际上,正是由于某些浏览器的显着性能优势(猜测是哪种),使用才真正在框架内流行。
Pointy

@Pointy有趣。我没有基准,但常识告诉我的innerHTML要慢一些:它必须被解析,验证,并转化为DOM元素,这东西createElement确实在第一个步骤。但是,如果您知道有任何其他基准测试,我会很乐意得到纠正。
Pekka 2010年

3
quirksmode基准测试有些过时了:quirksmode.org/dom/innerhtml.html
Pointy 2010年

@Pointy仍然:哇!完全违背了我的想法。干杯,将更新我的答案。
Pekka 2010年

1
感谢您对Pekka和Pointy的见识。这加强了我的观点,即innerHTML更快(除了编码经验外)。同样,在数组中使用html字符串也可以提供额外的性能提升和可读性。我想知道的是createElement是否有使用它的要点。
oninea

8

如果要在代码中保留引用,则应使用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很棒,因为它速度更快,大多数时间更容易阅读,但是您必须小心并谨慎使用。如果您知道自己在做什么,就可以了。


0

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

const container = document.getElementById("container");

const item_value = "some Value";

const item = `<div>${item_value}</div>`

container.innerHTML = item;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.