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

更新日期: 2025-12-28 阅读: 40 标签: 页面

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

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

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的效果才比较明显。

现代前端框架reactvue都使用了类似的思路。它们创建虚拟的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://fly63.com/article/detial/13309

js禁止页面回退的方法(使浏览器后退按钮失效)

这篇文章主要为大家详细介绍js禁止页面回退的方法(使浏览器后退按钮失效),具有一定的参考价值,感兴趣的小伙伴们可以参考一下

footer固定在页面底部的实现方法总结

footer固定在底部,footer保持在屏幕底部的四种方法实现:footer高度固定+绝对定位、footer高度固定+margin负值、footer高度任意+js、常用的纯css实现footer sticker

页面重定向的几种方法

html重定向就是通过各种的方法将各种网络请求重新定个方向转到其它位置。 在网站建设中,时常会遇到需要网页重定向的情况:像网站调整,如改变网页目录结构,网页被移到一个新地址

html页面实现返回上一页浏览位置

如果上一页是静态页面,可以用 history.go(-1)方法;页面采用了vue,页面每次加载都会去请求数据,用history.go(-1)方法返回上一页,上一页的页面因为重新请求数据,页面不会定位到上次浏览的位置;

在静态页面中使用 Vue.js

不使用Node.js, NPM, Webpack 等, 在静态页中使用Vue.js. 包括路由, 单文件组件. index.html做为项目的首页, 主要用来定义页面框架, 加载必需的css和script.这里使用element-ui的导航菜单组件搭建了一个页面框架.

js监听用户进入和离开当前页面

VisibilityChange 事件;用于用户是否离开当前页面;页面的 visibilityState属性可能返回三种状态 prerender,visible 和 hidden ;监听 visibility change 事件;页面变为不可见时触发

Flutter 之页面切换(基本路由)

一个应用程序通常由多个页面组成,而统一管理页面之间跳转的机制通常被称为路由管理或导航管理,在 Flutter 中,页面之间的跳转是通过 Route 和 Navigator 来管理的

解决webapck多页面内存溢出

因为自己的项目是基于vue-cli3进行开发,所以这里只讨论这种情况下的解决办法 ,在进行多页面开发的时候,项目刚开始阶段,因为文件较少,所以代码编译速度还行,但是随着项目逐渐增大

JS内嵌多个页面,页面之间如何更快捷的查找相关联的页面

假设parent为P页面,P页面有两个子页面,分别为B页面和C页面;B页面和C页面分别内嵌一个iframe,分别为:D页面和E页面,现在通过B页面的内嵌页面D的方法refreshEpage(eUrl)来加载内嵌页面E的内容.

Flutter页面跳转传参

路由(Route)在移动开发中通常指页面(Page),这跟web开发中单页应用的Route概念意义是相同的,Route在Android中通常指一个Activity,在iOS中指一个ViewController。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!