栈操作: function bar() { console.log(1); }
function foo() { console.log(2); far(); }
setTimeout(() => { console.log(3) });
foo(); 出栈入栈图:
队列,事件循环: console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise( function(resolve,reject){
console.log('promise1');
resolve()
}).then(function() {
console.log('promise2');
});
console.log('script end'); 这一段代码的输出是什么?
首先要确定哪些是宏任务,哪些是微任务:在js中,settimeout,setinterval,setimmediate(node环境中才有)属于标准的宏任务,而promise.then与prossis.nextTick(node)、mutation observe属于常见微任务,主要是需要异步的执行任务而又不需要分配一个新的 宏任务,这样便可以减小一点性能的开销js是单线程执行的,它的异步任务与多线程语言类似,也是将事件存放在队列中,依次执行,但是并不会开新线程执行,而是等待一个宏任务执行完毕,再从队列中取到另外一个宏任务执行。宏任务与微任务的关系就在于,event runloop 一次循环执行,会执行一个宏任务以,然后会执行当前宏任务内的所有微任务,最后循环结束,开启下一次循环。
回退再来看上面那段代码,很简单: script start promise1 script end proise2(微任务) settimeout(宏任务)
宏任务与宏任务 微任务与微任务 优先级问题:
setImmediate(()=>{
console.log('setImmediate1')
setTimeout(()=>{
console.log('setTimeout1') 复制代码
},0)
}) setTimeout(()=>{
console.log('setTimeout2')
process.nextTick(()=>{console.log('nextTick1')})
setImmediate(()=>{
console.log('setImmediate2')复制代码
})
},0) 如果从图中判断,由上自下,优先级一次排列,timer >io > check,那么我们可以判断:
1.主线程执行,setimmediate1进栈,判断异步执行,加入队列中并出栈,settimeout2进栈,判断异步执行加入到执行队列中,主线程执行完毕。
2.开启新的event loop ,执行timer异步模块,执行settimeout2,并将nextTick与setImmediate2加入到异步模块,settimout2执行结束立刻执行nextTick,然后进入check模块,执行完setImmediate1,并将settimout1加入队列,之后执行setImmediate2,结束后执行settimout1 但是执行结果并不如意
第一种: setImmediate1 setTimeout2 nextTick1 setImmediate2 setTimeout1
第二种: setImmediate1 setTimeout2 nextTick1 setTimeout1 setImmediate2
第三种:setTimeout2 nextTick1 setImmediate1 setImmediate2 setTimeout1
settimout与setimmediate并没有执行优先级,当settiemout设置触发时间为0的时候,浏览器仍旧会给它加上一个最低触发时间4ms,虽然都是立刻执行函数,如果settimout需要等待4ms执行,那么setimmediate先执行,如果不需要,settimout具有更高的优先级。 再来看一个例子: setImmediate(()=>{
console.log('setImmediate1')
})
setTimeout(()=>{
console.log('setTimeout3')
},0)
setImmediate(()=>{
console.log('setImmediate3')
})
setTimeout(()=>{
console.log('setTimeout4')
},0)
setImmediate(()=>{
console.log('setImmediate4')
}) 执行结果只有两种:
1.setImmediate1 setImmediate3 setImmediate4 setTimeout3 setTimeout4
2.setTimeout3 setTimeout4 setImmediate1 setImmediate3 setImmediate4
再一次说明settimout与setimmediate执行顺序是不定的,但是另外一点是,异步执行方式并不是完全按照加入队列的先后顺序执行,会执行完timer模块内的立刻执行函数与check模块内的立刻执行函数。这一点与老版本的node执行方式有些不一样,以前是一个循环完成后接上下一次循环,现在执行settimout会依次遍历所有的settimout执行完毕,setimmediate也是同样的操作
关于优先级的另一个比较清晰的版本:
观察者优先级
在每次轮训检查中,各观察者的优先级分别是:
idle观察者 > I/O观察者 > check观察者。
idle观察者:process.nextTick
I/O观察者:一般性的I/O回调,如网络,文件,数据库I/O等
check观察者:setImmediate,setTimeout
可以明确的一点是,nextTick执行优先级是高于pormise的,
new Promise(function (resolve, reject) {
console.log('promise')resolve()复制代码
}).then(function() {
console.log('then')复制代码
})
process.nextTick(()=>{console.log('nextTick1')})
执行结果始终是 promise nextTck1 then
在事件循环中有一点忘记提及的是gui渲染线程,虽然它不属于js线程,但是由于js单线程,gui渲染也被加入到队列中,作为一个任务使用,类似微任务,不会当成一个单独的宏任务执行,但是优先级最低,即一次event loop执行完主线程,以及所有当前线程微任务后,才执行gui渲染线程。