08月07, 2022

JS进阶(7)--高阶函数(3)--分时函数

骚操作:分时函数

在前面关于函数防抖和函数节流的讨论中,提供了限制函数被频繁调用的解决方案。下面将遇到另外一个问题,某些函数确实是用户主动调用的,但因为一些客观的原因,这些函数会严重地影响页面性能。

举个例子:创建 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>

效果: 2022-07-27 10.55.17

下面是数组分块的详细代码,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 条数据是分批创建添加的

2022-07-27 10.58.58

本文链接:http://www.yanhongzhi.com/post/js_ap_15.html

-- EOF --

Comments