Event loop
在上图中可以看到,setTimeout这类异步接口实际上不在JS引擎中,而是由浏览器中的Web(图中的V8是chrome中的JS引擎,safari、firefox则是各自的引擎,参考《主流浏览器内核及JS引擎》)
setTimeout(() => console.log(1) }, 0) console.log(0)
执行的过程:
-
然后「JS引擎线程」执行了console.log(0),控制台中输出了0
-
浏览器中的「定时器线程」接管该定时任务,当定时任务超时后,将回调函数即console.log(1)塞入到宏任务队列中,等待调度,如图中「2」
-
「event loop线程」检查JS执行栈是否为空,如果是,则将宏任务队列中的任务塞入JS执行栈中,如图中「8」
-
「JS引擎线程」处理执行栈中的console.log(1)代码,控制台中输出1
从这个流程可以看出,无论超时设置的多短,setTimeout中的回调函数必须等到下一次事件循环才能被处理,这就不难理解为什么0会在1之前输出了
promise.then函数
Promise.resolve().then(()=>{ console.log(2) }) console.log(0)
执行的过程:
-
「JS引擎线程」运行JS执行栈中执行如上代码,由于Promise首先执行了resolve函数变为终结状态,因此立即执行promise.then方法,此方法实际上是创建了一个微任务,并将console.log(2)塞入微任务队列中,如图「5」所示。
-
然后「JS引擎线程」执行了console.log(0),控制台中输出了0
-
「event loop线程」检查JS执行栈是否为空,如果是,则将微任务队列中的任务塞入JS执行栈中,如图中「8」
-
「JS引擎线程」处理执行栈中的console.log(2)代码,控制台中输出2
任务分类
JS事件循环中的任务分为两大类
-
宏任务,即task
-
微任务,即microTask,也称job
对于浏览器环境和Node环境,提供的接口会有些许差别
宏任务接口
接口 | 浏览器 | Node |
---|---|---|
I/O操作 | ? | ? |
setTimeout | ? | ? |
setInterval | ? | ? |
setImmediate | ? | ? |
requestAnimationFrame | ? | ? |
微任务接口
接口 | 浏览器 | Node |
---|---|---|
process.nextTick | ? | ? |
promise.then/catch/finally | ? | ? |
MutationObserver | ? | ? |
任务优先级
-
整体上,微任务的处理优先于宏任务。准确的说是微任务会在本此事件循环结束前处理,宏任务会在下次事件循环开始时处理。
-
在Node环境中,process.nextTick产生的微任务优先级最高,会被插到微任务队列的最前端优先处理;而setImmediate产生的宏任务优先级最高,会在所有宏任务之前执行