createElement 相对于innerHTML 的优势?

IT技术 javascript dom innerhtml createelement
2021-01-25 15:59:29

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

5个回答

除了安全之外,使用createElement而不是修改innerHTML(而不是扔掉已经存在的东西并替换它)还有几个优点,就像 Pekka 已经提到的:

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

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

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

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

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

如果您要进行大量添加,则绝对不想继续重置,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>
我通常避免innerHTML因为它的缺点。对于复杂的标记(比如建表),我通常会编写函数来生成标记的每个部分。例如,我有一个函数可以tr从每一行的数据中生成 a 然后我可能有另一个函数将行组合成一个表。每个函数都可以像make使用适当的参数调用一样简单如果性能成为问题,我可以更改函数以返回 HTML 字符串。
2021-03-18 15:59:29
@staticsan,这是一个很好的观点(该make函数还可以使事情变得更简单)。
2021-04-01 15:59:29
单独创建 DOM 片段肯定会加快插入速度。关于innerHTML,也可以/应该使用相同的方法。我通常将我的 HTML 字符串推送到一个数组,然后使用内置的数组连接函数(根据我的经验最快)连接元素,然后将其附加到 DOM。
2021-04-01 15:59:29
单独创建一个 DOM 树片段,然后在最后将它附加到真实的 DOM 上,也证明了速度优势。
2021-04-02 15:59:29
@Matthew Crumley:IYO,你认为你上面提到的优势(都是有效的)在实际使用中是否超过使用innerHTML的优势?更具体地说,假设您正在编写代码来动态构建表(一项非常常见的编码任务),您会为这两种方法使用 createElement 还是 innerHTML 构建片段。
2021-04-07 15:59:29

虽然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
});
我认为这个例子不足以证明任何事情。您需要使用 innerHTML 和手动构建来创建不同大小和复杂性(深度)的 DOM,如果结果一致,这将清楚地表明 innerHTML 更慢。此外,该测试是高度偏斜的,因为它强制渲染引擎使 body 标签的整个 DOM 无效,并重新构建它。而在另一个示例中,它只向 DOM 附加一个新节点,并没有触及 body 标签的其他部分。
2021-03-11 15:59:29
此外,即使分解为各个步骤,处理标签/属性的打开和关闭仍然很烦人。相反,字符串替换更干净,更防错。我在上面的答案中添加了一个示例。它可能会错过 IE 所需的字符串构造微优化,但除非您确实需要,否则不应为一个浏览器将代码复杂化。
2021-03-13 15:59:29
2021-04-03 15:59:29
@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("" );
2021-04-04 15:59:29
将代码放在反引号内var a = hello,但我可以找出你的代码。这看起来比单个连接的字符串更具可读性。
2021-04-06 15:59:29

用户bobince在他对 jQuery 的批评中提出了一些非常非常好的缺点

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

jQuery 不能完全归咎于此。毕竟,innerHTML 属性已经存在多年,并且已经证明比 DOM 更流行。但图书馆确实鼓励这种编码风格。

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

根据@Pointy 提供quirksmode 基准,InnerHTML在所有浏览器中都更快

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

感谢 Pekka 和 Pointy 的洞察力。这强化了我的观点,即innerHTML 更快(除了编码经验)。此外,在数组中使用 html 字符串还提供了额外的性能增益和可读性。我想知道的是 createElement 是否有使用它的任何要点。
2021-03-12 15:59:29
@Pointy 仍然:哇!完全违背我的想法。干杯,将更新我的答案。
2021-03-14 15:59:29
@Pointy 有趣。我没有基准,但常识告诉我的innerHTML要慢一些:它必须被解析,验证,并转化为DOM元素,这东西createElement确实在第一个步骤。但是,如果您知道任何其他方面的基准测试,我很乐意接受纠正。
2021-03-28 15:59:29
呃@Pekka你真的确定innerHTML要慢吗?我知道很长一段时间这绝对是错误的。innerHTML实际上,正是因为在某些浏览器中具有显着的性能优势(猜猜是哪个),在框架内使用才变得流行。
2021-04-01 15:59:29
那么 quirksmode 基准测试有点旧quirksmode.org/dom/innerhtml.html
2021-04-06 15:59:29

如果您想在代码中保留引用,您应该使用 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 很棒,因为它更快,而且大多数时候更容易阅读,但您必须小心谨慎地使用它。如果你知道你在做什么,你会没事的。

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

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

const item_value = "some Value";

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

container.innerHTML = item;
使用此方法时要注意 HTML 注入。
2021-03-30 15:59:29