insertAdjacentHTML() 启用更快的 HTML 代码片段插入

以下是由 Henri Sivonen 撰写的客座文章

在 Firefox 8 中,我们添加了对 insertAdjacentHTML() 的支持。这是 Internet Explorer 的一项古老功能,最近在 HTML5 中被正式化,然后被分解到 DOM 解析规范 中。坏消息是 Firefox 是最后一个实现此功能的主要浏览器。好消息是,由于其他主要浏览器已经实现了它,因此一旦 Firefox 8 更新发布给用户,您就可以无条件地开始使用它。

基本用法

insertAdjacentHTML(<var>position</var>, <var>markup</var>)HTMLElement DOM 节点上的一个方法。它接受两个字符串参数。第一个参数是 "beforebegin""afterbegin""beforeend""afterend" 之一,并给出相对于调用 insertAdjacentHTML() 的节点的插入点。第二个参数是一个包含 HTML 标记的字符串,该字符串将被解析为 HTML 片段(类似于分配给 innerHTML 的字符串)并插入到第一个参数给出的位置。

如果调用 insertAdjacentHTML() 的节点是 p 元素
文本内容为“foo”,则插入点将位于以下代码段中的注释位置

foo

"beforebegin""afterend" 位置仅在
节点位于树中且具有元素父节点时才有效。

例如,考虑以下代码

foo

此代码产生以下日志输出

foobar

好吧,这看起来并不特别。事实上,它看起来像是可以使用普通的 innerHTML 完成的事情。那么,当 <var>element</var>.innerHTML += "<var>markup</var>"; 已经可以工作时,为什么要费心使用 insertAdjacentHTML() 呢?

有两个原因。

  • insertAdjacentHTML() 不会破坏 DOM 中已有的内容。
  • insertAdjacentHTML() 更快。

避免 DOM 破坏

让我们首先考虑 DOM 破坏问题。当您执行 <var>element</var>.innerHTML += "<var>markup</var>"; 时,浏览器会执行以下操作

  1. 它通过序列化 <var>element</var> 的后代来获取 innerHTML 的值。
  2. 它将 += 右侧附加到字符串。
  3. 它删除 <var>element</var> 的子节点。
  4. 它解析包含旧后代的序列化以及一些新标记的新字符串。

旧的后代可能是通过脚本插入形成的子树,在序列化为 HTML 并重新解析时不会往返。在这种情况下,操作后,即使对于“旧”部分,树的形状也会不同。(例如,如果 <var>element</var> 有一个 p 子节点,而该子节点又有一个 div 子节点,则子树将不会往返。)此外,即使序列化和重新解析导致了看起来相同的树,解析器创建的节点也将与最初作为 <var>element</var> 子节点的节点不同。因此,如果 JavaScript 程序的其他部分持有对 <var>element</var> 后代的引用,则在执行 <var>element</var>.innerHTML += "<var>markup</var>"; 之后,这些引用将指向分离的节点,并且 <var>element</var> 将具有新的类似但不同的后代。

当使用 insertAdjacentHTML() 插入其他内容时,现有节点将保留在原位。

更好的性能

序列化和重新解析也是导致 <var>element</var>.innerHTML += "<var>markup</var>"; 模式出现性能问题的因素。每次附加一些内容时,<var>element</var> 中所有现有内容都会被序列化和重新解析。这意味着附加操作会越来越慢,因为每次都有越来越多的先前内容需要序列化和重新解析。

使用 insertAdjacentHTML() 可以产生很大的差异。出于测试目的,我从一个空的 div 开始,并运行一个循环,尝试在五秒钟内将尽可能多的推文附加到该 div 中。当您计算实现 @提及链接化、推文作者姓名、转发和收藏 UI 等所有标记时,推文实际上相当大。它的 HTML 源代码约为 1600 个字符——大部分是标记。

在我用于测试的计算机上,innerHTML 附加方式在整整五秒钟内仅设法附加了略高于 200 条推文。相比之下,insertAdjacentHTML("beforeend", ...) 附加方式在 5 秒内设法附加了近 30,000 条推文。(是的,是数百条与数万条。)显然,真实的 Web 应用永远不应该阻塞事件循环五秒钟——这仅用于基准测试。但是,这说明了随着越来越多的内容累积,每次都需要序列化和重新解析,innerHTML 附加方式是如何变得明显更慢的。

此时,一些读者可能会想知道 insertAdjacentHTML() 是否比 createContextualFragment() 提供任何优势。毕竟,从概念上讲,insertAdjacentHTML() 会创建一个片段并将其插入。

使用 createContextualFragment(),我的测试在五秒钟内仅设法处理了略高于 25,000 条推文,而使用 insertAdjacentHTML() 则处理了略低于 30,000 条推文。这是因为当插入点没有下一个兄弟节点时,Gecko 会加速 insertAdjacentHTML()(仅适用于 HTML——目前不适用于 XML)。"beforeend" 插入点永远没有下一个兄弟节点,并且始终会被加速(对于 HTML)。"beforebegin" 插入点始终有下一个兄弟节点(调用 insertAdjacentHTML() 的节点),并且永远不会被加速。对于 "afterbegin""afterend",操作是否会被加速取决于具体情况。

总之,您可以通过在当前使用 <var>element</var>.innerHTML += "<var>markup</var>"; 的地方使用 <var>element</var>.insertAdjacentHTML("beforeend", "<var>markup</var>"); 来提高 Web 应用的性能。

关于 Janet Swisher

Janet 是 MDN Web Docs 的社区负责人和项目经理。她于 2010 年加入 Mozilla,自 2004 年以来一直参与开源软件,自 20 世纪以来一直从事技术传播工作。她与丈夫和一只标准贵宾犬住在德克萨斯州的奥斯汀。

Janet Swisher 的更多文章……


15 条评论

  1. Alex Chang

    太棒了!终于等到这一天了。

    2011 年 11 月 9 日 21:32

  2. Fatih

    很高兴听到一些关于 HTML 代码片段插入的性能技巧。

    2011 年 11 月 9 日 22:00

  3. Maurizio DOmba

    感谢这篇文章……我已经很久没有读到这么有趣的文章了……insertAdjacentHTML() 真是一个很棒的技巧!

    2011 年 11 月 10 日 00:21

  4. Rene Kriest

    非常有趣的阅读,并提供了令人印象深刻的基准测试。

    所以也许几年后我们可以放弃 .innerHTML() ;)

    2011 年 11 月 10 日 01:16

  5. Theodor

    感谢您实现并解释性能问题!

    2011 年 11 月 10 日 01:58

  6. Ruturaj

    太好了,
    关于为什么它比 innerHTML 更好的文章写得不错!
    希望这个 HTML5 标准能被所有浏览器采用

    2011 年 11 月 10 日 04:10

  7. pd

    大概像 jQuery 这样的库现在可以通过为此使用它来变得更快更小 .prepend()、.append() 和类似的方法。有人可以确认吗?

    2011 年 11 月 10 日 05:17

  8. timmywil

    你能提供 createContextualFragment 与 insertAdjacentHtml 的性能测试吗?如果是这样,我想在其他浏览器中使用相同的测试来衡量它们的性能。

    2011 年 11 月 10 日 07:33

  9. Henri Sivonen

    @timmywil,我使用的测试位于 http://hsivonen.iki.fi/test/tweets/

    2011 年 11 月 11 日 03:42

  10. Richard Kimber

    为创新的 IE 功能欢呼!

    2011 年 11 月 11 日 06:01

  11. Manuel Strehl

    终于明白这个新方法是用来做什么的了。感谢您的概述!

    您能否估计一下,与 insertAdjacentHTML 相比,createDocumentFragment() + frag.innerHTML 然后是 appendChild(或 insertBefore)的性能会降低多少?

    2011 年 11 月 11 日 06:08

  12. Jamie Murphy

    终于!感谢您撰写了这篇内容详实的文章!:)

    2011 年 11 月 11 日 06:59

  13. Dmitry Pashkevich

    我的 Web 应用中有一段逻辑,其中元素的内部内容会完全重新渲染。您能否说明使用 insertAdjacentHtml 是否比 innerHTML 赋值具有性能优势?

    所以就像这样

    el.innerHTML=’New content’
    对比
    el.innerHTML=”;
    el.insertAdjacentHTML(‘afterbegin’, ‘New content’);

    如果我完全重新渲染元素或在创建后为其分配 inner html,是否仍然建议使用 insertAdjacentHTML?

    2011 年 11 月 11 日 07:23

  14. Henri Sivonen

    @Dmitry Pashkevich

    如果您无论如何都要删除元素之前的子节点,那么使用 insertAdjacentHTML 比 innerHTML 并没有什么优势。

    el.innerHTML=”;
    el.insertAdjacentHTML(‘afterbegin’, ‘New content’);

    效率略低于

    el.innerHTML=’New content’;

    当您想要保留节点的现有子节点并添加更多内容时,使用 insertAdjacentHTML 才有意义。

    2011 年 11 月 14 日 02:20

  15. Marcos Zanona

    该来的总会来!真的很棒 :)

    2011 年 11 月 16 日 08:38

本文评论已关闭。