一次渲染十万条数据:前端技术优化(上)

今天看了一篇文章,写的是一次性渲染十万条数据的方法,本文内容是对这篇文章的学习总结,以及知识点补充。

在现代Web应用中,前端经常需要处理大量的数据展示,例如用户评论、商品列表等。直接渲染大量数据会导致浏览器性能问题,如卡顿和延迟。本文将探讨几种优化策略,帮助开发者提高网页性能,优化用户体验。

方法一:通过document直接渲染十万条数据

示例代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>一次性渲染十万条数据</title>
</head>
<body><ul id="app"></ul>
</body>
<script>const app = document.querySelector('#app')let now = Date.now()// 方法一:一次性渲染十万条数据for (let i = 0; i < 100000; i++) {const li = document.createElement('li')li.innerText = `我是第${i + 1}条数据`app.appendChild(li)}console.log('js运行耗时', Date.now() - now)setTimeout(() => {console.log('dom渲染耗时', Date.now() - now)})</script>
</html>

结果

请添加图片描述

问题

当页面需要一次性渲染大量数据时,直接将所有数据渲染到DOM中会迅速消耗浏览器资源,造成性能瓶颈。这种方法虽然简单,但会导致浏览器响应缓慢,用户体验差。

补充知识1: JS事件循环之宏任务和微任务

在 JavaScript 中,事件循环是处理并发的机制,它允许执行非阻塞的操作。事件循环的核心概念包括宏任务(macro tasks)和微任务(micro tasks)。它们的执行顺序和机制十分重要,理解它们有助于更好地编写异步代码。

宏任务与微任务
  1. 宏任务 (Macro Task):

    • 宏任务是较大粒度的任务,它通常由以下几部分组成:
      • 整个脚本(执行的上下文)
      • setTimeout
      • setInterval
      • I/O 操作(如网络请求)
    • 宏任务的执行是按照创建顺序依次执行的。
  2. 微任务 (Micro Task):

    • 微任务是相对较小粒度的任务,通常用于处理短小的异步操作,主要由以下几部分组成:
      • Promise.then().catch()
      • MutationObserver
    • 微任务在当前宏任务执行完毕后立即执行,且在浏览器进行下一次重绘之前完成所有微任务。这意味着微任务的优先级高于宏任务。
执行顺序
  1. 首先,事件循环从宏任务队列中取出一个宏任务并执行。
  2. 执行完宏任务后,查看微任务队列,执行所有微任务,直到微任务队列为空。
  3. 一旦微任务队列为空,浏览器会进行渲染(重绘),然后再从宏任务队列中取出下一个宏任务。
  4. 重复这个过程,直到所有任务都完成。
示例

下面是一个简单的示例,帮助理解宏任务和微任务的执行顺序:

console.log('Start');setTimeout(() => {console.log('Timeout 1');
}, 0);new Promise((resolve, reject) => {console.log('Promise 1');resolve('Promise 1 resolved');
}).then((res) => {console.log(res);
});process.nextTick(() => {console.log('Next Tick');
});setTimeout(() => {console.log('Timeout 2');
}, 0);new Promise((resolve, reject) => {console.log('Promise 2');resolve('Promise 2 resolved');
}).then((res) => {console.log(res);
});console.log('End');
执行输出

执行上面的代码将会输出以下内容:

Start
Promise 1
Promise 2
End
Promise 1 resolved
Promise 2 resolved
Next Tick
Timeout 1
Timeout 2
  • 在 JavaScript 的事件循环中,宏任务的执行优先于微任务,但在宏任务完成后,微任务会立即执行,确保在下一个宏任务开始之前执行所有微任务。
  • 理解这一流程能够帮助开发者更好地预测和管理异步代码的执行顺序及其结果。

补充知识2: 执行渲染操作,更新界面为什么发生在执行 setTimeout之前?

在 JavaScript 中,事件循环和异步操作的处理方式是理解 setTimeout 和界面更新之间关系的关键。

界面更新的时机

在 JavaScript 中,界面更新通常在以下几个时刻发生:

  • 当主线程空闲时(即没有其他任务在执行时),浏览器会进行界面更新。
  • 在一个宏任务(如 setTimeout)执行之后,浏览器会检查微任务队列并执行所有的微任务,然后再进行界面更新。
为什么界面更新发生在执行 setTimeout 之前
  1. 执行顺序:当你调用 setTimeout 时,传入的回调函数不会立即执行,而是被放入宏任务队列中。主线程会继续执行当前的任务。
  2. 界面更新:在当前任务结束后,浏览器会检查是否有需要更新的界面。此时,如果有 DOM 的改变(例如通过某个函数修改了 DOM),浏览器会更新界面。
  3. 执行 setTimeout 的回调:在主线程空闲并完成微任务后,浏览器会从宏任务队列中取出 setTimeout 的回调并执行。
示例
console.log("Start");
setTimeout(() => {console.log("Inside setTimeout");
}, 0);
console.log("End");

输出顺序将是:

Start
End
Inside setTimeout

在 JavaScript 中,界面更新发生在当前任务完成后和 setTimeout 回调执行之前。理解这一点对于优化性能和确保用户界面流畅性非常重要。

补充知识3: 回流与重绘

页面的回流与重绘是指浏览器在渲染网页时的两种重要过程。

  1. 回流(Reflow):当页面的结构或内容发生变化时(比如增加、删除或修改元素的大小、位置等),浏览器需要重新计算元素的几何属性,以确定它们的位置和大小。这一过程称为回流。回流会影响整个文档的布局,因此较为耗性能。
  2. 重绘(Repaint):当元素的外观发生变化(比如背景色、字体颜色等)但并不影响布局时,浏览器只需重新绘制该元素,而无需重新计算其几何属性。这一过程称为重绘。重绘通常比回流消耗的性能要少。
    总结来说,回流是布局计算,重绘是外观更新。在优化网页性能时,应尽量减少这两个过程的发生。

方法二:分批渲染

为了解决直接渲染带来的性能问题,我们可以采用分批渲染的方法。通过将数据分成小块,逐一渲染,可以减轻浏览器的即时负担。

使用 setTimeout 进行分批渲染

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>一次性渲染十万条数据</title>
</head>
<body><ul id="app"></ul>
</body>
<script>const app = document.querySelector('#app')let now = Date.now()// 方法二:分批渲染十万条数据const total = 100000const loadOnce = 20const page = total / loadOnceconst index = 0function renderData(curTotal, curIndex) {let pageCount=Math.min(loadOnce,curTotal)setTimeout(() => {for (let i = 0; i < pageCount; i++) {const li = document.createElement('li')li.innerText = `我是第${i}条数据`app.appendChild(li)}renderData(curTotal-pageCount, curIndex + pageCount)})}renderData(total, index)</script>
</html>

结果

请添加图片描述

问题

当用户往下翻的时候有可能那一瞬间看不到东西

方法三、使用requestAnimationFrame替代setTimeout

requestAnimationFrame 是一种更高效的分批渲染方法,它允许在浏览器的绘制周期中执行动画和渲染,从而提高性能。

示例代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>一次性渲染十万条数据</title>
</head>
<body><ul id="app"></ul>
</body>
<script>const app = document.querySelector('#app')let now = Date.now()// 方法三:使用requestAnimationFrame渲染十万条数据const total = 100000const loadOnce = 20const page = total / loadOnceconst index = 0function renderData(curTotal, curIndex) {let pageCount=Math.min(loadOnce,curTotal)requestAnimationFrame(() => {for (let i = 0; i < pageCount; i++) {const li = document.createElement('li')li.innerText = `我是第${i}条数据`app.appendChild(li)}renderData(curTotal-pageCount, curIndex + pageCount)})}renderData(total, index)</script>
</html>

补充知识4: 什么是requestAnimationFrame?

requestAnimationFrame 是一个用于优化网页动画效果的 JavaScript 方法。它指示浏览器在下次重绘之前调用指定的回调函数,从而实现基于帧的动画效果。使用 requestAnimationFrame 的一个主要优点是它能够根据浏览器的绘制频率来调整动画的更新速率,从而使动画更加流畅和高效。

使用场景:
  1. 平滑动画:如果你在进行平移动画、旋转、缩放等效果时,使用 requestAnimationFrame 可以确保动画的每一帧都在浏览器的绘制周期内更新,这有助于避免由于使用 setTimeoutsetInterval 而导致的帧率不稳定。
  2. 减少 CPU 消耗requestAnimationFrame 还具有智能调节的功能,特别是在用户切换标签页或者浏览器窗口不在视野内时,它会自动停止调用回调函数,从而节省资源。

基本用法:

function animate() {// 更新动画状态// ...// 请求下一帧requestAnimationFrame(animate);
}
// 开始动画
requestAnimationFrame(animate);

在上面的代码中,animate 函数会执行动画的逻辑,并通过 requestAnimationFrame(animate) 请求下一帧的更新。这样,animate 函数会在浏览器准备好下一个绘制周期时被调用。

优势:

  • 帧率同步requestAnimationFrame 会使动画的帧率与浏览器的刷新率(通常是60帧每秒)同步,从而提高流畅性。

  • 性能优化:当页面不在视野中时,requestAnimationFrame 会暂停动画的执行,从而减少 CPU 和 GPU 的使用。

  • 简洁易用:API 简单直观,更容易使用来控制动画的生命周期。

总之,使用 requestAnimationFrame 是在现代网页应用中实现高性能动画的推荐方式。

方法四:利用 DocumentFragment

DocumentFragment 是一个轻量级的文档对象,可以用于在内存中组装一组节点,然后一次性添加到DOM中,减少DOM操作次数。

示例代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>一次性渲染十万条数据</title>
</head>
<body><ul id="app"></ul>
</body>
<script>const app = document.querySelector('#app')let now = Date.now()// 方法四:使用文档碎片渲染十万条数据const total = 100000const loadOnce = 20const page = total / loadOnceconst index = 0function renderData(curTotal, curIndex) {let documentFragment = document.createDocumentFragment()let pageCount=Math.min(loadOnce,curTotal)requestAnimationFrame(() => {for (let i = 0; i < pageCount; i++) {const li = document.createElement('li')li.innerText = `我是第${i}条数据`documentFragment.appendChild(li)}app.appendChild(documentFragment)renderData(curTotal-pageCount, curIndex + pageCount)})}renderData(total, index)</script>
</html>

总结

在处理大量数据渲染时,选择合适的方法至关重要。直接渲染虽然简单,但性能较差。分批渲染、requestAnimationFrameDocumentFragment 提供了更优的性能解决方案。开发者应根据具体情况选择最合适的方法,以确保应用的流畅性和用户体验。

补充知识5:什么是DocumentFragment?

DocumentFragment 是一个轻量级的文档对象,表示一个可以包含多个节点的虚拟容器。它不是文档中的实际部分,而是用来在内存中组装一组节点,然后一次性地将它们添加到文档中。这种做法可以提高性能,因为它减少了对 DOM 的多次操作,降低了重绘和重排的次数。

作用和优势:
  1. 性能优化:直接多次操作 DOM 会导致浏览器频繁地重绘和重排,从而降低性能。使用 DocumentFragment 可以将多个 DOM 操作合并为一个,从而显著提高性能。

  2. 内存管理DocumentFragment 只存在于内存中,直到你将它的内容添加到实际的 DOM 中。这使得在构建大型 DOM 结构时更节省内存。

  3. 灵活性:你可以使用 DocumentFragment 来临时组装和修改多个节点,然后一次性插入到 DOM 中,保持 DOM 的一致性。

基本用法:

下面是一个简单的例子,展示如何使用 DocumentFragment 来添加多个节点到 DOM 中:


// 创建一个 DocumentFragmentconst fragment = document.createDocumentFragment();// 创建几个新的元素const li1 = document.createElement('li');li1.textContent = 'Item 1';const li2 = document.createElement('li');li2.textContent = 'Item 2';const li3 = document.createElement('li');li3.textContent = 'Item 3';// 将元素添加到 DocumentFragment 中fragment.appendChild(li1);fragment.appendChild(li2);fragment.appendChild(li3);// 将 DocumentFragment 追加到现有的 DOM 中document.getElementById('myList').appendChild(fragment);

在上面的示例中,使用 DocumentFragment 创建并组合了多个 li 元素,最后一次性将它们添加到一个列表中。这样可以避免在将每个 li 添加到 DOM 时引起的多次重排和重绘。

DocumentFragment 是一个非常有用的工具,适用于需要频繁与 DOM 交互时,帮助保持性能和优化操作。在需要插入或修改多个节点时,使用 DocumentFragment 是个不错的选择。

参考文章:
https://juejin.cn/post/7407763018471948325

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1538111.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【C++登堂入室】类和对象(下)

目录 一、 再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 1.3 explicit关键字 二、static成员 2.1 概念 2.2 特性 三、友元函数 3.1 友元函数 3.2 友元类 四、内部类 五、 再次理解类和对象 结尾 一、 再谈构造函数 1.1 构造函数体赋值 在创建对象时&#xf…

【C++】多态的认识和理解

个人主页 文章目录 ⭐一、多态的概念&#x1f384;二、多态的定义及实现1.多态的构成2.实现多态的条件3.虚函数的概念4.虚函数的重写和覆盖5.析构函数的重写6.协变7.override和 final关键字8.重载、重写/覆盖、隐藏这三者的区别 &#x1f3e0;三、纯虚函数和抽象类的关系&#…

三目运算及简单案例

//三目运算是用来简化判断的 //所谓的三目 有三个表达式组成 //表达式一 条件表达式 返回的结果是布尔 //表达式二 条件表达式结果为true 时 返回的结果 //表达式三 条件表达式结果为false 时 返回的值 int age 20; //获取用…

树莓派驱动之spi回环测试

开启spi sudo raspi-config选择Interfacing options,选择spi打开 lsmod可以看到spi_bcm2835 短接MISO和MOSI 编写回环代码spitest.c #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <string.h>…

【Python机器学习】NLP信息提取——命名实体与关系

我们希望计算机能够从文本中提取信息和事实&#xff0c;从而略微理解用户所说的内容。例如&#xff0c;当用户说“提醒我星期一浏览***.org网站”&#xff0c;我们希望这句话触发当天后下一个周一的日程或者提醒的操作。 要触发上述操作&#xff0c;需要知道“我”代表一种特定…

【devops】devops-gitlab之部署与日常使用

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

新任项目经理,如何快速接手项目

新任项目经理踏上管理岗位&#xff0c;面临着前所未有的挑战与机遇。如何快速适应角色&#xff0c;有效管理团队&#xff0c;确保项目顺利推进&#xff0c;是每一位新任项目经理必须面对的。 一、深入了解项目与团队 1、项目定位与规划 新任项目经理应对项目进行全面的调研与…

初识zookeeper

Zookeeper介绍 Zookeeper是Apache Hadoop项目下的一个子项目&#xff0c;是一个树形目录服务。 Zookeeper是一个分布式的、开源的分布式应用程序的协调服务。 Zookeeper提供的主要功能包括&#xff1a; 配置管理分布式锁集群管理 Zookeeper数据模型 Zookeeper客户端常用命…

windows使用tcpdump.exe工具进行抓包教程

windows主机安装一些抓包工具可能有些不方便&#xff0c;这里有一个tcpdump.exe工具直接免安装&#xff0c;可以直接使用进行抓包。&#xff08;工具下载见 附件&#xff09; tcpdump.exe使用教程 如下&#xff1a; 1&#xff1a;tcpdump -D 可查看网络适配器(注意前面的编号)…

分治算法专题(一)——快速排序之【三路划分】

目录 1、分治算法简介 2、算法应用【leetcode】 2.1 题一&#xff1a;颜色分类 2.1.1 算法原理 2.2.1 算法代码 2.2 题二&#xff1a;排序数组——数组分三块原理 2.2.1 算法原理 2.2.2 算法代码 2.3 题三&#xff1a;数组中的第K个最大元素 2.3.1 算法原理 2.3.2 算…

各大平台统遭入侵??区块链市场遭攻击损失近3亿!

今年&#xff0c;全球发生多起骇人听闻的勒索入侵软件攻击事件&#xff0c;黑客组织利用各种手段和技术&#xff0c;不断试图突破网络安全防线&#xff0c;窃取敏感信息、破坏系统运行&#xff0c;甚至进行勒索和敲诈&#xff0c;使得网络安全问题日益凸显其重要性和紧迫性。 S…

Mysql分组取最新一条记录

文章目录 Mysql分组取最新一条记录1. 数据准备1. 方法1&#xff1a;使用子查询获取每个组的最大时间戳&#xff0c;然后再次查询获取具体记录&#xff08;如果时间戳是唯一的&#xff09;2. 方法2&#xff1a;使用窗口函数&#xff08;MySQL 8.0&#xff09;3. 方法3&#xff1…

TikTok跨境电商营销新策略:品牌联盟与影响力经济的结合

随着TikTok成为全球化的社交和电商平台&#xff0c;也给跨境卖家提供了新的商机。境电商通过与其他知名品牌、网红或KOC建立品牌联盟&#xff0c;能够有效实现资源共享、优势互补&#xff0c;并推动市场扩张&#xff0c;带来更大的商业价值和品牌影响力。本文Nox聚星将和大家探…

鸿蒙开发协调布局CollapsibleLayout

鸿蒙开发协调布局CollapsibleLayout 首先鸿蒙我暂时没找到官方提供的协调布局&#xff0c;所以得自己自定义。 一、思路 可滚动头部、粘性头部、可滚动内容布局 可折叠区域高度可滚动头部高度-粘性头部高度 二、效果图 鸿蒙开发协调布局CollapsibleLayout 三、关键代码 //…

优思学院|如何从零开始自己学习六西格玛?

优思学院为学习六西格玛管理的学员&#xff0c;精心推荐了几本由浅入深、系统全面的书籍&#xff0c;帮助大家从入门到精通&#xff0c;逐步掌握六西格玛这一强大的管理工具。无论你是刚接触六西格玛的初学者&#xff0c;还是想在专业领域提升的高级学员&#xff0c;这几本书都…

【ARM】Trustzone和安全架构

Trustzone的基本概念&背景和历史 什么是Trustzone&#xff1f; 什么是TEE&#xff1f; Trustzone是一个技术&#xff0c;是一个技术的设计&#xff0c;一个安全架构&#xff0c;既不是软件也不是硬件。 TEE (Trusted Execution Environment) 可信执行环境。就是依托Trust…

速响低代码平台:升级营销管理系统,开启高效无忧新体验!

当前日新月异的商业环境&#xff0c;企业面临着前所未有的挑战与机遇。随着市场竞争的日益加剧和企业业务的不断拓展&#xff0c;传统的营销方式和管理手段逐渐显露出其局限性&#xff0c;难以适应快速变化的市场需求。 数据收集难&#xff1a;传统的营销管理缺乏对客户数据的收…

战神诸神黄昏9月19日登录PC端! 手机怎么玩战神诸神黄昏

9月19日&#xff0c;《战神&#xff1a;诸神黄昏》正式登录PC端&#xff0c;这是一部动作冒险游戏。要是你想随时随地在手机或平板上也能玩《战神&#xff1a;诸神黄昏》&#xff0c;可以使用网易GameViewer远程帮你实现。 网易GameViewer远程作为一款专为游戏玩家打造的远程软…

轻松让U盘数据恢复的教程:一步步指导,快速找回丢失文件

在日常使用U盘的过程中&#xff0c;我们可能会不小心删除或格式化了一些重要文件&#xff0c;导致数据丢失。面对这种情况&#xff0c;很多人可能会感到焦虑和无助。但其实&#xff0c;只要掌握了正确的方法&#xff0c;U盘数据的恢复并不复杂。本文将为大家提供一份详细的教程…

LIN总线CAPL函数——校验和段(Checksum)测试(linGetChecksum)

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…