当前位置:首页 > 前端 > 正文内容

【JS】什么是Document Fragment?

virtualman2周前 (08-23)前端70

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 操作的性能,尤其是在需要批量添加或移动节点时。

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。