但愿你能了解什么是 Event Loop(事件循环),以及对 Web Worker 有所了解,以便更容易吸取javascript
W3C 性能组规定:执行时长大于 50ms 的任务,定义为长任务html
那么咱们如何对长任务进行优化?前端
js 是单线程语言,它的做用主要用于操做DOM。 js执行也很是简单(从上往下执行),但 js 里也有异步方法,好比 xhr 、 setTimeout 等等。java
setTimeout 的做用是:将当前任务推入任务队列,当主线程同步的代码执行完成后判断 setTimeout 设置的时间有没有到,若是到了将任务推出队列执行。git
下面是 Event Loop 示例图:github
因为长任务执行时间长,会阻塞主线程,用户能感受到页面卡顿,因此咱们常常会采用 setTimeout 把长任务推入任务队列,等到同步代码执行完成后再处理长任务:web
const t = setTimeout(() =>{
clearTimeout(t);
// 长任务
},0)
复制代码
因为前端处理长任务的场景并很少,通常由服务端处理完后给到前端,因此 setTimeout 能解决大部分的长任务问题,可是该方法不能用于时间过长的任务,好比一个任务须要秒级时长,用户仍然会感受页面卡死。api
下面这个场景也会致使卡顿:浏览器
#box {
width: 100px;
height: 100px;
background: green;
}
<div id="box"></div>
// 动画 大概须要2s跑完
var start = null;
var element = document.getElementById('box');
element.style.position = 'absolute';
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 200) + 'px';
if (progress < 2000) { window.requestAnimationFrame(step); } } window.requestAnimationFrame(step); setTimeout(function() { // 500ms的长任务 var now = performance.now(); while (now + 500 > performance.now()) {}
}, 100);
复制代码
上面代码中使用 setTimeout 来处理长任务,可是页面中有个须要 2s 跑完的动画效果,当执行到 setTimeout 时,动画仍在运行,这时在队列里的长任务被推出并执行致使主线程阻塞,动画出现卡顿。bash
时间分片并非某个 api,而是一种技术方案,它能够把长任务分割成若干个小任务执行,并在执行小任务的间隔中把主线程的控制权让出来,这样就不会致使UI卡顿。
React 的 Fiber 技术核心思想也是时间分片,Vue 2.x 也用了时间分片,只不过是以组件为单位来实施分片操做,因为收益不高 Vue 3 把时间分片移除了。
为了好理解,先写段长任务代码,将主线程阻塞 1秒钟:
const start = performance.now();
let count = 0;
while (performance.now() - start < 1000) {}
console.log('done!');
复制代码
该段脚本霸占主线产长达 1s 的时间 咱们能够封装一个 ts 方法,让这个长任务被分割成多个小任务执行:
ts(function* (){
const start = performance.now();
let count = 0;
while (performance.now() - start < 1000) {
yield;
}
console.log('done!');
})()
复制代码
先看看效果吧:
从图里看到,一个长任务被切成了诺干个小任务,在每一个小任务间隔中把主线程的控制权交出来,这样就不会致使页面卡顿
基于 Generator 函数的执行特性,咱们很容易使用它来实现一个时间分片函数:
function ts(gen) {
if (typeof gen === 'function') gen = gen();
if (!gen || typeof gen.next !== 'function') return;
return function next() {
const start = performance.now();
const res = null;
do {
res = gen.next();
} while (!res.done && performance.now() - start < 25);
if (res.done) return;
setTimeout(next);
};
}
复制代码
上面代码中,作了一个 do while:若是当前任务执行时间低于 25ms 则多个任务一块儿执行,不然做为一个任务执行,一直到 res.done = true 为止。
代码核心思想:经过 yield 关键字能够将任务暂停执行,并让出主线程的控制权;经过setTimeout
将未完成的任务从新放在任务队列中执行
文章开头提过 js 是单线程,但浏览器不是。 Web Worker 容许咱们在后台建立独立于主线程的其余线程,因此咱们能够把一些费时费力的长任务交给 Web Worker。
因为共享线程浏览器支持状况较差,本章咱们只介绍专用线程。
咱们建立一个文件夹,并在里面建立 index.html 和 worker.js 目录以下:
.
├── index.html
└── worker.js
复制代码
index.html 代码:
<input type="text" id="ipt" value="" />
<div id="result"></div>
<script> const ipt = document.querySelector('#ipt'); const worker = new Worker('worker.js'); ipt.onchange = function() { // 经过postMessage发送消息 worker.postMessage({ number: this.value }); }; // 经过onmessage接收消息 worker.onmessage = function(e) { document.querySelector('#result').innerHTML = e.data; }; </script>
复制代码
worker.js 代码:
// self 相似主线程中的 window
self.onmessage = function(e) {
self.postMessage(e.data.number * 2);
};
复制代码