V8是如何实现微任务的?
基于V8的UI线程架构模型,JavaScript延伸了很多新技术,其中应用最广泛的就是宏任务和微任务。
一、宏任务
宏任务:就是指消息队列中等待被主线程执行的任务。
每个宏任务在执行时,V8都会重新创建调用栈,随着宏任务中函数的调用,调用栈也会随之变化,最终,该宏任务执行结束时,整个调用栈又会被清空,接着执行下一个宏任务。
常见宏任务:定时器,各种事件。
二、微任务
微任务:微任务可以看做是一个需要异步执行的函数,执行时机是在主函数执行结束之后,当前宏任务结束之前。
V8为什么要引入微任务???
由于主线程执行消息队列中宏任务的时间颗粒度太粗了,无法胜任一些对精度和实时性要求较高的场景,所以引入微任务在实时性和效率之间做一个有效的平衡。同时,使用微任务可以让我们改变异步编程的方式,以同步的方式编写异步代码。
常见微任务:Promise,async/await,MutationObserver
微任务相关知识:
微任务是基于消息队列,事件循环,UI线程架构,堆栈的,同时基于微任务,又可以延伸出promise,generaor,协程,async/await。
三、主线程-调用栈
调用栈是一种数据结构,用来管理主线程上执行的函数的调用关系。
1 | /** |
由于调用栈空间在内存中是连续的,所以通常会限制调用栈的大小。
1 | /** |
当函数嵌套过深时,调用栈中的执行上下文会堆积在栈中,最终导致栈溢出。
当然我们可以用setTimeout来解决栈溢出问题。原理就是将同步函数调用改成异步函数调用,这样foo函数就会被封装成宏任务,添加到消息队列中,主线程每次在执行消息队列中的宏任务时,都会重新创建调用栈,这样就不会存在栈溢出问题。
但是,这又存在一个问题,如果某些宏任务的执行时间过久,会影响到后面宏任务的执行。因为宏任务的执行时间是不可控的,我们不知道多要多久才能执行结束。
四、微任务解决宏任务执行时机不可控问题
微任务会在当前的宏任务快要执行结束时才执行,这样可以比较精确的控制回调函数的执行时机。
那么,V8如何实现微任务的???
V8会为每个宏任务维护一个微任务队列。当V8执行一段JS代码时,会为这段代码创建一个全局执行上下文,微任务队列就存放在该执行上下文中。当通过Promise.resolve等创建一个微任务时,该微任务就会被V8添加进微任务队列中,等这段代码快要执行结束时,V8会先检查微任务队列中是否有任务要执行,有的话,就按顺序执行微任务,最后销毁该执行上下文。
1 | /** |
1 | /** |
当在微任务中循环触发新的微任务时,会导致页面成卡死状态,但并不会栈溢出。因为当前宏任务始终无法执行结束,导致后面的宏任务无法执行,同时,取出微任务,执行又添加微任务,当前微任务执行结束退出,又重复之前操作,这样页面就体现出卡死的状态,同时并不会栈溢出。
参考链接
李兵《图解 Google V8》:https://time.geekbang.org/column/intro/100048001