先有一个宏观的认识 > > >
JS是单线程引擎,在线程中拥有唯一一个事件循环(web workder涉及到了多线程,再做补充)
JS代码执行过程中,依靠函数调用栈顺序执行JS代码,遇到异步操作(如ajax,setTimeout等)会抛给宿主环境(如浏览器),调用Web API执行异步事件,浏览器执行结束后将事件的回调函数放入任务队列(task queue),调用栈为空时会从任务队列中拿出一个回调函数放入调用栈执行。
事件循环只有一项简单的工作-监测调用栈和回调队列。如果调用栈是空的,它会从回调队列中取得第一个事件然后入栈,并有效地执行该事件。
事件循环中的这样一次遍历被称为一个 tick。每个事件就是一个回调函数。
一个线程中,事件循环是唯一的,但是任务队列可以有多个。
任务队列
任务队列分为macro-task
(宏任务)和micro-task
(微任务),新标准中称为task和jobs。
macro-task
包括script(代码整体)、setTimeout、setInterval、IO、UI rendering。
micro-task
包括process.nextTick、Promise、MutationObserver(HTML5新特性)。
setTimeout/Promise为任务源,进入任务队列的是他们的执行任务(如回调函数)。
ES6 作业概念
ES6 介绍了一个被称为『作业队列』的概念。它位于事件循环队列的顶部。你极有可能在处理 Promises(之后会介绍) 的异步行为的时候无意间接触到这一概念。
现在我们将会接触这个概念,以便当讨论 Promises 的异步行为之后,理解如何调度和处理这些行为。
像这样想象一下:作业队列是附加于事件循环队列中每个 tick 末尾的队列。事件循环的一个 tick 所产生的某些异步操作不会导致添加全新的事件到事件循环队列中,但是反而会在当前 tick 的作业队列末尾添加一个作业项。
这意味着,你可以添加延时运行其它功能并且你可以确保它会在其它任何功能之前立刻执行。
一个作业也可以在同一队列末尾添加更多的作业。理论上讲,存在着作业循环的可能性(比如作业不停地添加其它作业)。
为了无限循环,就会饥饿程序所需要的资源直到下一个事件循环 tick。从概念上讲,这类似于在代码里面书写耗时或者死循环(类似 while(true))。
作业是有些类似于 setTimeout(callback, 0) 小技巧,但是是以这样的方式实现的,它们拥有明确定义和有保证的执行顺序:之后且尽快地执行。
执行规则
1、来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的(?)。
2、事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
3、其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。
异步执行的运行机制
1、所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
2、主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
3、一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
4、主线程不断重复上面的第三步。主线程不断重复上面的第三步。
参考how-javascript-works系列教程中的一篇的动画展示,代码如下:
console.log('Hi');
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
以及两个过程图解(出自其他两个博客):
有些博客中写的是script运行到比如setTimeout就放入事件队列中,这个说的有问题,是在你定义的timeout事件(事件由Web API执行,即宿主环境如浏览器等执行,与js引擎无关)结束之后,将回调函数放入事件队列中,所谓macroTask/microTask是早期为了区分promise,process.nextTick等和其他如setTimeout执行顺序不一样而定义的,标准中并没有macroTask这么一说。
参考: