前端知识点总结-事件轮询

510 阅读2分钟

在聊这个话题之前,先来看一道老生常谈的打印题

console.log('script start');
setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});
console.log('script end')

这题的正确答案是script start, script end, promise1, promise2, setTimeout。

我们都知道JavaScript是单线程语言,也就是一次只能做一件事。但是有些情景并不能立马就得到结果,需要等待一段时间,比如ajax请求,比如I/O,比如定时器(setTimeout 和 setInterval)。这时候如果程序一直等到结果返回了再进行后面的动作就会造成资源浪费。JavaScript的解决方案是异步,将需要等待的任务先放在一边,先执行同步任务。待异步任务的返回了结果时,如果此时同步任务已经执行结束,就开始执行异步回调。

简而言之,同步任务直接执行,异步任务放到一边,异步任务有结果返回后就会将回调函数放到事件队列中。待到同步任务执行完毕,JavaScript就会读取任务队列的内容,开始执行。

那么是否所有异步任务都遵循先进先出的原则呢?显然不是。ES6引入了宏任务和微任务的概念,微任务的优先级是高于宏任务的,执行顺序是:首先读取所有的微任务,然后读取一个宏任务,再读取所有的微任务,再读取一个微任务。。。

var r = new Promise(function(resolve, reject){
    console.log("a");
    resolve()
});
setTimeout(()=>console.log("d"), 0)
r.then(() => console.log("c"));
console.log("b")

以这段小代码为例,首先会执行new Promise里面的console.log('a'),遇到setTimeout时,把回调放到任务队列中;接着是promise的then内容,这里又是一个异步任务,放到任务队列中;再就是console.log('b'),直接打印。至此,同步任务执行完毕,js会先读取微任务队列中的console.log('c');微任务执行完成之后,再读取宏任务队列中的console.log('d')

为了说明微任务始终优先于宏任务,我们来看下面这个例子

setTimeout(()=>console.log("d"), 0)
var r = new Promise(function(resolve, reject){
    resolve()
});
r.then(() => { 
    var begin = Date.now();
    while(Date.now() - begin < 1000);
    console.log("c1") 
    new Promise(function(resolve, reject){
        resolve()
    }).then(() => console.log("c2"))
});

即使c2晚于d一秒加入任务队列,依然会先执行

那么js里面的宏任务和微任务分别有哪些呢? 微任务包括:process.nextTick(Nodejs), Promises, Object.observe, MutationObserver;宏任务(tasks)包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering

最后总结一下。js的运行机制是,同步任务直接执行,异步任务放到任务队列中轮询执行。任务又分宏任务和微任务,微任务的优先级高于宏任务,js总是先执行完所有的微任务再执行一个宏任务。

That's all, thanks a lot.