未优化之前的版本:
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>