一、javascript 引擎的执行机制
javascript 是运行在浏览器上的单线程脚本语言, 它本身为了操作DOM而设计的,所以不允许多个线程同时进行,否则浏览器会不知道究竟该如何执行。
而根据js引擎自上而下依次执行的执行顺序,当一段代码解析时间过长时,就会产生阻塞,导致接下来的代码无法如期执行,会出现卡死的状态,用户体验非常不好,如果是因为CPU占用太多导致的卡死也就罢了,关键是CPU并没有很忙,所以js有了异步加载。
我们先来看一下js的运行机制:
- event loop 会自上向下执行,遇到异步任务时会将它放放 event table 中,主线程继续执行
- event table会为这个异步任务注册函数,当满足触发条件时再将它推到event queue中
- 当主线程执行完毕后,会到event queue中查看是否有异步任务,如果有,就会被推到主线程中继续执行
怎么样,思路是不是清晰啦,那我们来看一个简单的例子吧
console.log(1)
setTimeout(() => {
console.log(4)
}, 0);
setTimeout((() => {
console.log(2)
return ()=>{
console.log(3)
}
})(), 100);
// 1 2 4 3
这段代码的执行顺序是:1,2,4,3。首先执行1都没有疑问了,然后event loop判断4是异步任务,把它放到event talbe中,继续执行,执行到2的时候,event loop判断2是一个立即执行函数,所以2会在主线程中执行,而return中是返回了一个异步任务,所以3也会放在event table中,这样主线程就执行完了,然后event loop 会去 event queue中查看任务队列,(这里要给大家说一下,当setTimeout 第二个参数为零的时候,会自动改为20),所以当2微秒后,setTimeout会放到 event queue中,event loop 将它推到主线程中执行,3在1毫秒结束后,也会被推到event queue中,当4执行完之后,event loop 会再监听 event queue,发现有3,再被推到event queue中,就这样反复地循环,直到程序执行完毕。
二、microtask 和 macrotask
任务队列就是我们刚刚说的 event queue 它并不是一个单独体,而是被分为两个部分 microtask 和macrotask:
macrotask(宏任务队列 )
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame
- I/O
- UI rendering
microtask(微任务队列 )
- process.nextTick
- Promise
- Object.observe
- MutationObserver
任务队列的执行顺序是:开始 -> 取task queue第一个task执行 -> 取microtask全部任务依次执行 -> 取task queue下一个任务执行 -> 再次取出microtask全部任务执行 ->
for( var i = 0; i < 10; i++){
setTimeout(() => {
console.log('i1=',i)
}, 0);
setTimeout((() => {
console.log('i2=',i)
return ()=>{
console.log(3)
}
})(), 100);
}
new Promise((resolve, reject)=>{
console.log(5)
for(let i = 0; i<= 10000; i++){
i ===10000 && resolve()
}
}).then(()=>{
console.log(6)
})
// 1
// 0 1 2 3 4 5 6 7 8 9
// 5
// 6
// 10 10 10 10 10 10 10 10 10
// 3 3 3 3 3 3 3 3 3 3 3
解释一下:event loop 会先执行1,当遇到for循环中 i1 是 会被放到宏任务队列中,再次执行,遇到 i2时,判断它是一个立即执行函数,会被放在主任务中执行,也就是依次执行,i3 作为一个返回函数100毫秒后被放到宏任务中,执行到promise时,event loop会执行函数体5,而执行到 resolve时,判断它也是一个异步任务,被放在微任务队列中,这样主线程执行完毕。
event loop空闲了,就首先执行微任务中的5,(如果微任务中有宏任务,会再被加入到宏任务中,执行宏任务,再执行微任务。),当微队列所有任务执行完毕后,会到宏任务中执行i1,在1毫秒后,再执行 3。
有没有感到很晕呀~哈哈哈哈,那就平时多练习吧!再给你们一个例子:
for( var i = 0; i < 10; i++){
setTimeout(() => {
console.log('i1=',i)
new Promise((resolve,reject)=>{
resolve()
}).then(()=>{
console.log(8)
})
}, 0);
}
//1
// i1= 10
// 8
// i1= 10
// 8
// i1= 10
// 8
// i1= 10
// 8
// i1= 10
// 8
// i1= 10
// 8
// i1= 10
// 8
// i1= 10
// 8
// i1= 10
// 8
// i1= 10
// 8
在我们知道 js是先执行microtask 后执行 macrotask的 ,那为什么还会这样打印呢?其实刚刚我们已经说过了 js的执行机制是 开始 -> 取task queue第一个task执行 -> 取microtask全部任务依次执行 -> 取task queue下一个任务执行 -> 再次取出microtask全部任务执行