骚操作:分时函数
在前面关于函数防抖和函数节流的讨论中,提供了限制函数被频繁调用的解决方案。下面将遇到另外一个问题,某些函数确实是用户主动调用的,但因为一些客观的原因,这些函数会严重地影响页面性能。
举个例子:创建 WebQQ 的 QQ 好友列表。列表中通常会有成百上千个好友,如果一个好友用一个节点来表示,在页面中渲染这个列表的时候,可能要一次性往页面中创建成百上千个节点。
在短时间内往页面中大量添加 DOM 节点显然也会让浏览器吃不消,看到的结果往往就是浏览器的卡顿甚至假死。示例代码如下:
const ary = [];
for (let i = 1; i <= 1000; i++) {
ary.push(i); // 假设 ary 装载了 1000 个好友的数据
}
const renderFriendList = function (data) {
for (let i = 0, l = data.length; i < l; i++) {
let div = document.createElement('div');
div.innerHTML = i;
document.body.appendChild(div);
}
};
renderFriendList(ary);
这个问题的解决方案之一就是使用数组分块技术(又被称之为分时函数),数组分块是一种使用定时器分割循环的技术,为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。
在数组分块模式中,数组变量本质上就是一个"待办事宜"列表,它包含了要处理的项目。使用shift()
方法可以获取队列中下一个要处理的项目,然后将其传递给某个函数。如果在队列中还有其他项目,则设置另一个定时器,并通过arguments.callee
调用同一个匿名函数。
数组分块的重要性在于它可以将多个项目的处理在执行队列上分开,在每个项目处理之后,给予其他的浏览器处理机会运行,这样就可能避免长时间运行脚本的错误。一旦某个函数需要花 50 ms 以上的时间完成,那么最好看看能否将任务分割为一系列可以使用定时器的小任务。
下面的timeChunk()
函数让创建节点的工作分批进行,比如把 1 秒钟创建 1000 个节点,改为每隔 200 ms 创建 8 个节点。以下是数组分块模式的简易代码:
<body>
<div id="myDiv"></div>
</body>
<script>
function timeChunk(array, process) {
setTimeout(function () {
// 取出下一个条目并处理
const item = array.shift();
// 这里的 this 指代的 window 对象
// 将传入进来的 printValue 方法作为 window 对象的一个方法
process.call(this, item);
// 若还有条目,再设置另一个定时器
if (array.length > 0) {
setTimeout(arguments.callee, 200);
}
// console.log(array);
}, 100);
}
const data = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
];
function printValue(item) {
const div = document.getElementById("myDiv");
div.innerHTML += item + "<br>";
}
timeChunk(data, printValue);
</script>
效果:
下面是数组分块的详细代码,timeChunk()
函数接受 3 个参数。第 1 个参数是创建节点时需要用到的数据,第 2 个参数是封装了创建节点逻辑的函数,第 3 个参数表示每一批创建的节点数量:
<script>
// 该分时函数接收 3 个参数:数据、创建节点逻辑的函数、每一批创建的节点数量
// 该分时函数将返回一个计时函数,根据数据是否已经创建完节点来决定是否停止
const timeChunk = function (ary, fn, count) {
let obj, t;
let len = ary.length;
const start = function () {
// 创建规定数量的节点
for (let i = 0; i < Math.min(count || 1, len); i++) {
let obj = ary.shift();
fn(obj);
}
};
return function () {
t = setInterval(function () {
// 如果全部节点都已经被创建好
if (ary.length === 0) {
return clearInterval(t);
}
start();
}, 200); // 分批执行的时间间隔,也可以用参数的形式传入
};
};
// 最后我们进行一些测试,假设我们有1000个好友的数据
// 我们利用 timeChunk 函数,每一批只往页面中创建 8 个节点
const ary = [];
for (let i = 1; i <= 1000; i++) {
ary.push(i);
}
const renderFriendList = timeChunk(
ary,
function (n) {
const div = document.createElement("div");
div.innerHTML = n;
document.body.appendChild(div);
},
8
);
renderFriendList();
</script>
效果:可以看到 1000 条数据是分批创建添加的
Comments