写在前面
面过前端的小伙伴们都见过这么一道关于异步的小题:javascript
for(var i = 0; i<10; i++){
setTimeout(function(){
console.log(i)
},1000)
}
复制代码
稍微了解异步的同窗都会对答案呼之欲出。BUT! 若是问题升级为:html
setTimeout(function(){
console.log(1)
},0)
new Promise(function executor(resolve) {
console.log(2)
for(var j = 0;j<100;j++){
j=99&&resolve()
}
console.log(3)
}).then(function(){
console.log(4)
})
console.log(5)
复制代码
是否是稍微的有那么点小蒙圈? 别着急,本篇内容结束后以上问题都再也不是事儿。解决以上问题的要点,首先须要清楚Javascript异步处理模块,事件队列,以及事件环-Eventloop.前端
进程是操做系统分配资源和调度任务的基本单位,线程是创建在进程上的一次程序运行单位,一个进程上能够有多个线程。
进程和线程属基础概念,再也不赘述。有个最生动易懂的解释,详情请移步参考:进程与线程的一个简单解释java
对Javascript而言,从诞生之日起,它就是单线程的。为何呢?举个小栗子:若是能够多线程,a线程要添加某DOM节点,b线程要删除它,浏览器怎么办?难道要精分? 因此说,单线程减小了不少情境的复杂性。
既然js是单线程的,它又以什么样的规则来处理并发的任务呢?千军万马要过独木桥的时候,不能靠力气来抢路。单线程,任务多,就得有个规矩来安排你们。因而秘密终于被咱们发现——事件环(Event Loop)就要出马了。 对于首次据说这个概念的同窗,有必要铺垫下基础知识:node
对象被分配在一个堆中,即用以表示一个大部分非结构化的内存区域。web
函数调用造成一个栈帧;vim
栈的特色:先进后出(First in, last out,具体是怎样让那些函数先入后出的?看下图会恍然大明白,图中的帅哥是Philip Roberts,看解释,别光看脸! api
一个 JavaScript 运行时包含了一个待处理的消息队列。 每个消息都有一个为了处理这个消息相关联的函数promise
说到了任务队列,就到了重点部分:事件环(Eventloop)了浏览器
Defined by webappapis:
To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section.
任务队列以事件环来协调事件,用户交互,脚本,渲染,网络等。
Each 'thread' gets its own event loop, so each web worker gets its own, so it can execute independently
This is a constantly running process that checks if the call stack is empty. Imagine it like a clock and every time it ticks it looks at the Call Stack and if it is empty it looks into the Event Queue.
简单来讲,每一个线程都有他本身的事件环,浏览器也拥有本身的事件环;事件环是一种运行时机制,它像个钟表同样,每滴答一下,就去看看stack
里有没有事须要处理,没有的话就去事件队列(Event Queue
)看看有没有事作。
此处你们须要明白,事件环并非定死的某个规矩,须要根据不一样的运行时进行本身的一套规则。
There are two kinds of event loops: those for browsing contexts, and those for workers.--from webapis.
如node下的事件环与浏览器环境下的事件环就不是相同的规则。必定要记清楚哦!首先讨论浏览器事件环。
依据webapis里声明的事件环(event loop)存在时的执行步骤,归纳以下:
一图顶千言,做图解释下:
看似按照这个规则,咱们文章开头的问题就能够有答案了。 回顾一下:
setTimeout(function(){
console.log(1)
},0)
new Promise(function executor(resolve) {
console.log(2)
for(var j = 0;j<100;j++){
j=99&&resolve()
}
console.log(3)
}).then(function(){
console.log(4)
})
console.log(5)
复制代码
代码执行后,按照执行顺序:
Explained by webappapis
Each event loop has a microtask queue. A microtask is a task that is originally to be queued on the microtask queue rather than a task queue. There are two kinds of microtasks: solitary callback microtasks, and compound microtasks.
翻译:每一个eventloop都有一个微任务队列,微任务最初是被放到微任务队列而不是任务队列。
这里你们请把这句话放在内心,这是解决宏任务的一把钥匙所在。
虽然知道任务队列分为宏任务,微任务,可是一直未找到宏任务的定义,直到看到stackoverflow上的解释。
One go-around of the event loop will have exactly one task being processed from the macrotask queue (this queue is simply called the task queue in the WHATWG specification).
每一个事件环必须有一个来自宏任务队列的任务正在执行。 这里将宏任务解释为whatwg上定义的任务队列.
是否是有点迷惑,这不对啊? 虽然把任务队列分红宏任务,微任务理解比较容易,但我认为这后面更大的秘密才是我苦苦找寻的真相。 若是只执行宏任务队列,那微任务队列怎么执行呢?
这正好与上文中提到微任务队列是单独的队列,跟任务队列不是一回事符合。 整个任务能够理解为,全部的宏任务放在一个宏任务队列(即任务队列),处理完一个宏任务(从sccript开始),将微任务队列(包含当时全部的微任务)压入任务队列(宏任务队列)并执行,以后再取下一个任务队列(宏任务)中的宏任务。
因而,咱们的题目能够翻译成如下解决思路:
eventloop
中为何必须在全部的微任务microtask
都执行结束后再取新的宏任务macrotask
呢?这涉及microtask
的执行机制:
step2中作了明确的解释,当微任务队列 的标记被写为true以后,只要microtask的队列不为空,eventloop中的当前任务就会按顺序执行microtask队列中的全部任务。
这里能够看到二者的一点区别,微任务microtask
队列是独立的一个队列,在eventloop执行过程当中才进入到任务队列task queue
一次执行。
没有尽兴的朋友推荐如下几篇好文,这些是我我的认为讲解事件环,异步事件队列等最为具体清晰的文章:
Best reference:
- Tasks, microtasks, queues and schedules by Jake Archibald
- How does Javascript event work? or
Help, I'm stuck in an event-loop by Philip Roberts- stackoverflow.com/questions/2…
其余参考
但愿你们看文本文能有收货。欢迎批评指正。
Author: Yanni Jia
Nickname: 很是兔
Email: 385067638@qq.com