一、Event Loop是什么?
由于Javascript是单线程的,所有的任务在一个线程上完成。这会导致同一时间只能完成一个任务,如果前一个任务比较耗时,将会导致页面卡顿、假死等问题,用户体验极差,这被称之为“同步模式”和“堵塞模式”。
为此,Javascript提供了一个叫做Event Loop(事件循环)的机制来解决单线程运行时阻塞的问题。相反,这叫做“异步模式”和“非堵塞模式”。Event Loop在浏览器和Node环境下会略有差异。
二、Event Loop原理
由于Javascript的单线程特点,当有很多任务时,各个任务会进行排序依次处理。而大部分的延时都花费在I/O传输上。
解决的办法就是主线程挂起等待的任务,先运行后面的任务,这就是异步的过程,Javascript中的任务一般分为两大类:
- 同步任务
- 异步任务
其中,同步任务会直接被压入执行栈(execution context stack)被主线程执行,前一个完成后,执行后一个任务;
异步任务不进入主线程,而会进入任务队列(task queue)。只有主线程的任务全部执行完毕之后,才会通过事件和回调函数来执行任务队列中的任务。
Event Loop实际上就是指主线程不断去任务队列中读取事件的过程。
三、异步任务
前面提到,任务一般分为同步任务和异步任务。同步任务直接进行执行栈被主线程执行,而异步任务进入任务队列等待事件回调,而异步任务又可以分为:
- 宏任务(MarcoTask): setTimeout,setInterval,setImmediate,I/O,UI交互事件;
- 微任务(MicroTask): process.nextTick,Promise.then
异步任务的执行顺序又因为是否是宏任务还是微任务有所不同。
浏览器中的Event Loop
在浏览器中,主线程是执行完执行栈中的所有同步任务,确认执行堆中没有同步任务之后;
检查微任务中是否有任务process.nextTick和Promise.then等任务,执行完所有的微任务之后;
转而检查宏任务中是否有任务,如果有,则每次取出第一个宏任务加入到执行栈中,之后再清空执行栈,检查微任务,以此循环… …
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
let promise = new Promise(resolve => {
console.log(3);
resolve();
}).then(data => {
console.log(4);
}).then(data => {
console.log(5);
});
console.log(6);
// 1 3 6 4 5 2
Node.js中的Event Loop
Node是一个基于V8引擎的javascript运行环境,JS代码解析后代码调用的Node API,Node会交给libuv库处理,它将不同的任务分配给不同的线程,形成一个 Event Loop,以异步的方式将任务的执行结果返回给V8引擎,再将结果返回给用户。
因为Node环境下的差异性,我们可以理解为将浏览器中的宏任务更加细分为六个阶段。
Node.js环境下与浏览器下最大的区别在于,浏览器是每执行一个宏任务就会检查微任务;Node.js是清空当前所处阶段的队列,即执行所有task,再去检查微任务,具体看下面这个例子。
setImmediate(() => {
console.log(1);
process.nextTick(() => {
console.log(4);
});
});
process.nextTick(() => {
console.log(2);
setImmediate(() => {
console.log(3);
});
});
// 2 1 3 4