前言
我们知道 JavaScript 是一门单线程的非阻塞脚本语言,单线程意味着 js 在执行任何任务时,都只有一个主线程来处理所有的任务。
那么 JavaScript 引擎是如何实现这一点的呢?这就要说到 JavaScript 的事件循环机制(Event Loop)了:
基本流程
我们可以用个流程图来描述一下这个过程:
详细解释如下~~
- 同步任务和异步任务分别进入不同的执行环境,同步任务进入主线程,异步任务进入 Event Table 并注册函数
- 当指定的事情完成后,Event Table 会将这个函数移入到 Event Queue 中
- 主线程内的任务执行完毕,就会去 Event Queue 中读取对应的函数,进入主线程执行
- 上述过程不断重复,形成 Event Loop
从上面的解释我们可以初步得出一个结论:
同步任务比异步任务先执行
用代码来表现如下:
console.log(1);
setTimeout(()=>{
console.log(2);
}, 0);
console.log(3);
// 输出 1 3 2
我们接下来可以了解一些更复杂点的例子,了解之前我们需要知道 宏任务
和 微任务
宏任务和微任务
宏任务和微任务均属于异步任务,同属于队列,他们主要区别在于:** 微任务比宏任务更先执行 **
宏任务有:setTimeout
, setInterval
, setImmediate
,I/O
,UI rendering
微任务有:process.nextTick
,promise.then
,MutationObserver
到这里我们可以来看个小🌰:
console.log('1');
setTimeout(() => {
console.log('2')
}, 1000);
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('3');
}, 0);
console.log('4');
resolve();
console.log('5');
}).then(() => {
console.log('6');
});
console.log('7');
输出:
1 4 5 7 6 3 2
首先执行到第一个 setTimeout
的时候,js 会把这个任务放进队列等待执行,接下去执行同步函数,在执行到 new Promise 的时候会立马新建一个 promise 对象并立即执行。所以会输出 1,4,5,而 then 会放在微任务队列中,两个 setTimeout
会放到宏任务队列中。
所以执行顺序是:同步 - promise 同步 - 微任务 then - 宏任务 setTimeout
所以输出结果: 1 - 4 - 5 - 7 - 6 - 3 - 2
再来一个复杂一点的🌰:
console.log(1)
process.nextTick(() => {
console.log(8)
setTimeout(() => {
console.log(9)
})
})
setTimeout(() => {
console.log(2)
new Promise(() => {
console.log(11)
})
})
let promise = new Promise((resolve,reject) => {
setTimeout(() => {
console.log(10)
})
resolve()
console.log(4)
})
fn()
console.log(3)
promise.then(() => {
console.log(12)
})
function fn(){
console.log(6)
}
1 - 4 - 6 - 3 - 8 - 12 - 2 - 11 - 10 - 9
执行顺序:先从头到尾找出第一层级的同步任务 - 找出第一层级的微任务(包括里面的同步任务) - 找出第一层级的宏任务(包括里面的同步任务)
如果第一层的宏任务微任务中有深层次的宏任务微任务,push 到 queue 中。