用好DocumentFragment,解决页面卡顿问题

摘要:很多前端开发者都遇到过这样的问题:页面需要展示大量数据时,滚动和操作会变得很卡。最近我就帮同事解决了一个类似的问题。他做的用户管理后台要显示上千条数据,每次打开页面都要等很久,滚动时一卡一卡的。

很多前端开发者都遇到过这样的问题:页面需要展示大量数据时,滚动和操作会变得很卡。最近我就帮同事解决了一个类似的问题。他做的用户管理后台要显示上千条数据,每次打开页面都要等很久,滚动时一卡一卡的。

查看代码后,我发现他是这样写的:

for(let i = 0; i < data.length; i++) {
    const row = createTableRow(data[i]);
    table.appendChild(row);
}

这段代码的问题很明显。每次循环都会向页面添加一行数据,浏览器每添加一次就要重新计算一次页面布局。数据量小的时候感觉不出来,但数据多了就会明显卡顿。

要解决这个问题,可以用DocumentFragment。


什么是DocumentFragment?

DocumentFragment可以理解为一个临时的容器。我们先把要添加的DOM元素放到这个容器里,等所有元素都准备好后,一次性添加到页面中。这样做只需要一次页面重新计算,性能会好很多。

它的工作原理很简单。当我们直接操作页面上的DOM时,浏览器必须马上更新显示。但如果先在DocumentFragment里操作,浏览器就不会立即更新,因为DocumentFragment不在页面显示范围内。等所有操作完成,再把整个DocumentFragment的内容添加到页面,浏览器只需要更新一次。

看一个具体的例子:

// 创建临时容器
const fragment = document.createDocumentFragment();

// 在容器中添加元素,不会触发页面更新
for(let i = 0; i < 100; i++) {
    const item = document.createElement('div');
    item.textContent = `第 ${i} 条数据`;
    fragment.appendChild(item);
}

// 一次性添加到页面,只触发一次页面更新
container.appendChild(fragment);

这样写,无论添加100条还是1000条数据,页面都只更新一次。


DocumentFragment的几个特点

第一,它是一个独立的容器。在把它的内容添加到页面之前,你在里面做的任何操作都不会影响页面显示。

第二,它的内容是“转移”而不是“复制”。当你把DocumentFragment的内容添加到页面后,DocumentFragment自己就变空了。这意味着不会出现重复的元素,也不会浪费内存。

const fragment = document.createDocumentFragment();
const div = document.createElement('div');
fragment.appendChild(div);

console.log(fragment.childNodes.length); // 输出:1

document.body.appendChild(fragment);

console.log(fragment.childNodes.length); // 输出:0,内容已经转移到页面

第三,在DocumentFragment中的元素不能触发事件。因为事件需要元素在页面中才能生效。只有把DocumentFragment的内容添加到页面后,上面绑定的事件才会起作用。


实际应用场景

  1. 渲染大量数据

这是最常用的场景。比如用户列表、商品列表、数据表格等。我们做过一个测试,渲染500个商品条目,原来需要800毫秒,使用DocumentFragment后只需要300毫秒。用户感觉页面响应快了很多。

  1. 创建复杂组件

比如要创建一个弹窗,里面包含遮罩层、内容区、关闭按钮等多个元素。可以先把所有元素在DocumentFragment中组装好,再一次性添加到页面。

function createModal(content) {
    const fragment = document.createDocumentFragment();
    
    // 创建遮罩层
    const overlay = document.createElement('div');
    overlay.className = 'modal-overlay';
    
    // 创建弹窗内容
    const modal = document.createElement('div');
    modal.className = 'modal';
    modal.innerHTML = content;
    
    // 组装
    overlay.appendChild(modal);
    fragment.appendChild(overlay);
    
    // 一次性添加到页面
    document.body.appendChild(fragment);
    
    return overlay;
}
  1. 移动多个元素

如果需要把一组元素从一个地方移到另一个地方,用DocumentFragment也很方便。

function moveItems(fromElement, toElement) {
    const fragment = document.createDocumentFragment();
    
    // 把元素逐个移到临时容器
    while(fromElement.firstChild) {
        fragment.appendChild(fromElement.firstChild);
    }
    
    // 一次性添加到新位置
    toElement.appendChild(fragment);
}
  1. 处理HTML字符串

有时候我们需要把HTML字符串转换成DOM元素。可以先用innerHTML解析,再用DocumentFragment处理。

function htmlToFragment(html) {
    const fragment = document.createDocumentFragment();
    const tempDiv = document.createElement('div');
    
    tempDiv.innerHTML = html;
    
    // 转移所有子元素
    while(tempDiv.firstChild) {
        fragment.appendChild(tempDiv.firstChild);
    }
    
    return fragment;
}


需要注意的地方

虽然DocumentFragment很好用,但也不是所有情况都需要用它。

如果只是添加几个元素,直接操作DOM可能更简单明了。一般来说,当需要操作超过50个元素时,使用DocumentFragment的效果才比较明显。

现代前端框架如React、Vue都使用了类似的思路。它们创建虚拟的DOM树,在内存中完成所有更新,然后一次性同步到真实DOM。了解DocumentFragment的原理,能帮助我们更好地理解这些框架的工作方式。


如何调试

在浏览器的开发者工具中,DocumentFragment显示为#document-fragment。虽然它不在页面DOM树中显示,但你可以在控制台查看它的内容。

实际效果对比

我们做了一个简单的性能测试:

  • 添加10个元素:两种方法差异很小

  • 添加100个元素:DocumentFragment快约30%

  • 添加1000个元素:DocumentFragment快约60%

  • 添加10000个元素:DocumentFragment快约70%

数据量越大,优势越明显。


什么时候使用

建议在以下情况考虑使用DocumentFragment:

  1. 需要渲染大量数据时

  2. 需要创建复杂的UI组件时

  3. 需要批量移动或修改多个元素时

  4. 对页面性能要求较高时

简单的使用原则

记住这个原则:如果操作会触发多次页面重新计算,就考虑用DocumentFragment合并成一次操作。

比如原来要添加100行表格数据,会触发100次页面更新。改成先用DocumentFragment收集所有行,再一次性添加,就只触发1次页面更新。


总结

DocumentFragment是一个简单但实用的工具。它能有效减少页面卡顿,提升用户体验。虽然现在有各种前端框架,但理解这些基础API的原理仍然很有价值。

下次当你需要操作大量DOM元素时,可以试试DocumentFragment。你会发现,有时候最简单的办法就是最有效的办法。

记住,好的性能优化往往来自对基础知识的深入理解。DocumentFragment就是这样一个值得掌握的基础知识。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13309