【JS】什么是Document Fragment?
DocumentFragment
是 Web API 中的一个接口,表示一个没有父级的最小化文档对象。它被设计为一个轻量级的“文档片段”容器,可以用来存储一组节点,通常用于高效地进行 DOM 操作。
核心概念
- 虚拟容器:
DocumentFragment
本身不是一个完整的文档,也不是实际 DOM 树的一部分。它就像一个临时的“篮子”或“容器”,用来存放一组 DOM 节点。 - 高效操作:当你需要向 DOM 中添加多个节点时,如果逐个添加,每次添加都可能触发一次页面重排(reflow)和重绘(repaint),这会严重影响性能。使用
DocumentFragment
,你可以先将所有要添加的节点都添加到这个片段中,然后一次性将整个片段插入到 DOM 中。这样,浏览器只会触发一次重排和重绘,从而大大提高性能。 - 插入时“解包”:当你将一个
DocumentFragment
插入到 DOM 中时,插入的是它的子节点,而不是DocumentFragment
本身。DocumentFragment
容器本身不会成为 DOM 树的一部分。
创建 DocumentFragment
最常用的方法是使用 Document
对象的 createDocumentFragment()
方法:
const fragment = document.createDocumentFragment();
使用场景和示例
场景1:高效批量插入节点
假设你需要向一个列表中添加 1000 个 <li>
元素。
低效方式(不推荐):
const list = document.getElementById('myList');
for (let i = 0; i < 1000; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
list.appendChild(item); // 每次循环都直接操作 DOM,可能导致 1000 次重排
}
高效方式(使用 DocumentFragment
):
const list = document.getElementById('myList');
const fragment = document.createDocumentFragment(); // 创建片段
for (let i = 0; i < 1000; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
fragment.appendChild(item); // 将节点添加到片段中,不触发 DOM 更新
}
// 一次性将所有节点插入到 DOM
list.appendChild(fragment); // 或者 list.append(fragment)
// 此时,1000 个 <li> 节点被插入,但浏览器通常只进行一次重排
场景2:模板克隆与操作
有时可以结合 template
元素使用 DocumentFragment
。
<template id="myTemplate">
<div class="item">
<h3></h3>
<p></p>
</div>
</template>
<ul id="container"></ul>
const template = document.getElementById('myTemplate');
const container = document.getElementById('container');
const fragment = document.createDocumentFragment();
// 假设我们有一些数据
const data = [
{ title: 'Title 1', content: 'Content 1' },
{ title: 'Title 2', content: 'Content 2' }
];
data.forEach(itemData => {
// 深度克隆模板内容(返回一个 DocumentFragment)
const clone = template.content.cloneNode(true);
// 修改克隆的内容
clone.querySelector('h3').textContent = itemData.title;
clone.querySelector('p').textContent = itemData.content;
// 将克隆的片段添加到主片段中
fragment.appendChild(clone);
});
// 一次性插入所有内容
container.appendChild(fragment);
其他创建方式
template.content
属性:<template>
元素的content
属性返回一个DocumentFragment
,其中包含了模板的 DOM 子树。Range.extractContents()
:从Range
对象中提取内容时,返回一个DocumentFragment
。Range.cloneContents()
:克隆Range
对象中的内容时,返回一个DocumentFragment
。
注意事项
- 事件监听器:如果你给
DocumentFragment
中的节点添加了事件监听器,这些监听器在片段被插入到 DOM 后仍然有效。 - 脚本执行:如果
DocumentFragment
中包含<script>
标签,当片段被插入到 DOM 时,这些脚本通常不会自动执行。这是出于安全考虑。如果需要执行脚本,需要手动处理(例如,重新创建并插入 script 元素)。 - 现代替代方案:虽然
DocumentFragment
非常有用,但在某些简单场景下,现代 JavaScript 的Element.append()
、Element.prepend()
等方法也接受多个节点作为参数,提供了另一种批量操作的方式。但对于复杂的、需要构建大量节点的场景,DocumentFragment
仍然是最佳实践之一。
DocumentFragment
是一个强大的工具,用于优化 DOM 操作的性能,尤其是在需要批量添加或移动节点时。