未优化之前的版本:

let i = 0;let start = Date.now();function count() {  // do a heavy job  for (let j = 0; j < 1e9; j++) {    i++;  }  alert("Done in " + (Date.now() - start) + 'ms');}count();

上述 count 函数里的 for 循环的 i 累加,是一个 CPU 密集型工作,在执行结束之前,JavaScript 引擎工作队列里的其余工作,没有机会失去执行。

优化版本

将 i 从 1 累加到 1e9 的工作,拆解成 1000 个小的子工作。每个子工作执行结束之后,调用 setTimeout 调度本身,这样工作队列里其余工作有机会失去执行。

let i = 0;let start = Date.now();function count() {  // do a piece of the heavy job (*)  do {    i++;  } while (i % 1e6 != 0);  if (i == 1e9) {    alert("Done in " + (Date.now() - start) + 'ms');  } else {    setTimeout(count); // schedule the new call (**)  }}count();
  • 第一轮工作执行:i=1...1000000
  • 第二轮工作执行:i=1000001..2000000

以此类推。

当初,如果在引擎忙于执行第 1 局部时呈现新的辅助工作(例如 onclick 事件),它会排队,而后在第 1 局部实现时执行,而后再执行下一部分。 在 count 执行之间,应用 setTimeout 定期返回事件循环,为 JavaScript 引擎提供了调度执行其余工作的机会,以对其余用户操作做出反馈。

let i = 0;let start = Date.now();function count() {  // move the scheduling to the beginning  if (i < 1e9 - 1e6) {    setTimeout(count); // schedule the new call  }  do {    i++;  } while (i % 1e6 != 0);  if (i == 1e9) {    alert("Done in " + (Date.now() - start) + 'ms');  }}count();

为浏览器脚本拆分 CPU 密集型工作的另一个益处是咱们能够显示进度批示。

如前所述,只有在以后运行的工作实现后才会绘制对 DOM 的更改,无论以后运行的工作须要多长时间能力执行结束。

上面是一个例子:

<div id="progress"></div><script>  function count() {    for (let i = 0; i < 1e6; i++) {      i++;      progress.innerHTML = i;    }  }  count();</script>

对 i 的更改要等到函数执行实现后才会显示,所以咱们只会看到最初一个值。

但咱们也可能想在工作期间展现一些货色,例如一个进度条。

如果咱们应用 setTimeout 将沉重的工作拆分为多个局部,那么 i 会在多个局部执行之间绘制进去。

<div id="progress"></div><script>  let i = 0;  function count() {    // do a piece of the heavy job (*)    do {      i++;      progress.innerHTML = i;    } while (i % 1e3 != 0);    if (i < 1e7) {      setTimeout(count);    }  }  count();</script>