以下是由 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>";
时,浏览器会执行以下操作
- 它通过序列化
<var>element</var>
的后代来获取innerHTML
的值。 - 它将
+=
右侧附加到字符串。 - 它删除
<var>element</var>
的子节点。 - 它解析包含旧后代的序列化以及一些新标记的新字符串。
旧的后代可能是通过脚本插入形成的子树,在序列化为 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 世纪以来一直从事技术传播工作。她与丈夫和一只标准贵宾犬住在德克萨斯州的奥斯汀。
15 条评论