赞美效应
夸你夸你就夸你
标题:JavaScript 中分时函数的优势:解决创建大量节点的性能问题
在网页开发中,我们经常需要操作 DOM 元素,其中一个常见的任务是动态创建大量节点。但是,如果不加以控制,一次性创建大量节点可能会导致浏览器性能问题,甚至导致页面卡顿或崩溃。在本文中,我们将探讨这个问题,并介绍一种解决方案——分时函数。
为了演示问题的严重性,让我们先来实现一个简单的按钮,点击该按钮后会创建 10 万个节点。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>创建大量节点</title>
</head>
<body>
<button id="createNodesBtn">创建节点</button>
<div id="container"></div>
<script>
const createNodesBtn = document.getElementById('createNodesBtn');
const container = document.getElementById('container');
const datas = new Array(10000).fill(0).map((_, index) => index);
createNodesBtn.addEventListener('click', () => {
for (const i of datas) {
const node = document.createElement('div');
node.innerText = i;
container.appendChild(node);
}
});
</script>
</body>
</html>
在点击按钮后,浏览器将尝试一次性创建并插入 10 万个 <div>
节点到页面中,会造成主线程阻塞,这将严重影响页面性能。
造成卡顿的原因是什么呢? 我们知道,浏览器的渲染机制是:主线程负责渲染页面,渲染过程中,主线程会阻塞,直到渲染完成。现在,我有一个任务,比较长,导致这个任务没有给浏览器流出任何的空间,所以浏览器的渲染时机被延后了,多出的这段时间就是卡顿。
如何优化呢?
分时函数是一种用于分割任务执行时间的技术。它将一个耗时较长的任务分成多个小任务,在一段时间内逐步执行,从而减轻了单次任务对浏览器性能的影响。
requestIdleCallback
requestIdleCallback是浏览器提供的一种API,用于在浏览器空闲时段内执行特定的回调函数。这个API的主要目的是优化网页性能,特别是针对那些需要在主进程空闲时进行的任务,以避免阻塞主线程影响页面渲染和用户交互。
window.requestIdleCallback(function callback(deadline) {
// deadline 对象包含了两个属性:timeRemaining 和 didTimeout
// timeRemaining 返回一个估算值,表示在下一次屏幕刷新之前,回调还可以运行多久
// didTimeout 表示是否因为超时而调用(如果设定了超时时间)
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
const task = tasks.shift();
task();
}
if (tasks.length > 0) {
// 如果还有任务没完成,再次请求回调
requestIdleCallback(callback);
}
}, { timeout: 5000 /* 可选,超时时长 */ });
在上述代码中,requestIdleCallback
接受两个参数:
callback:当浏览器进入空闲状态时调用的函数。该函数接收一个deadline对象作为参数,通过它来了解剩余可用时间。
options:可选参数对象,包含一个可选的timeout属性,指定回调函数等待执行的最大时间(毫秒),超过这个时间后无论浏览器是否空闲都会执行回调。
requestIdleCallback
非常适合用来处理一些低优先级的工作,比如预加载图片、日志记录、批处理DOM更新等,确保这些任务不会影响到关键的用户体验。但需要注意的是,并非所有浏览器都支持此API,实际开发中可能需要引入polyfill或者采用其他兼容方案。
下面是一个使用分时函数的示例代码,用于优化上面的创建节点任务:
function chunk(data, taskHandler, scheduler) {
if (!data.length) {
return
}
let i = 0;
// 开启下一个分片执行
function _run() {
if (i >= data.length) {
return;
}
// 一个渲染帧中,空闲时开启分片执行
requestIdleCallback((idle) => {
while(idle.timeRemaining() > 0 && i < data.length) {
taskHandler(data[i], i);
i++;
}
// 此次分片完成
_run();
}
}
_run();
}
createNodesBtn.addEventListener('click', () => {
const datas = new Array(100000).fill(0).map((_, index) => index);
const taskHandler = (i) => {
const div = document.createElement('div');
div.innerText = i;
document.body.appendChild(div);
}
chunk(datas, taskHandler)
});
总的来说,分时函数是一种非常有效的优化技术,特别是在处理大量节点创建等耗时任务时,它能够帮助我们提升页面性能,改善用户体验。在实际开发中,我们应该根据具体情况合理运用分时函数,以达到更好的效果。