前几天听公司一个公司三年的前端说“今天又学到了一个知识点-微任务、宏任务”,我问他这是什么东西,因为在吃饭他浅浅的说了下,当时没太理解就私下学习整理一番,因为谈微任务、宏任务必谈到事件循环,因而就有了这篇博客。javascript
在谈到事件循环机制以前咱们须要知道一些基础知识就是:前端
其实js是单线程在它做为脚本语言操做dom的时候就决定了。那么此时就有一个性能问题,那么js在浏览器端是如何处理这个问题的呢?同时,js在后台Node中又是如何解决的呢?这就是本篇须要介绍的事件循环机制,这里我将分别以浏览器和Node两个方面来分析。java
在讲解事件循环以前先谈谈js中同步代码、异步代码的执行流程。node
js引擎在执行经过代码的过程当中,会安装顺序依次存储到一个地方去,这个地方就叫作执行栈
,当咱们调用一个方法的时候,js会生成一个和这个方法相对应的上下文(context)。这个执行环境中存在着这个方法的私有做用域,上层做用域的指向,方法的参数,这个做用域中定义的变量以及这个做用域的this对象。promise
function a() { console.log("method a execute..."); } function b() { a(); } function c() { b(); } c();
以上面例子分析:js在执行的时候会有一个全局上下文,咱们这里就称为GContext,下面分析步骤浏览器
ok,上面是同步代码的执行,上面会涉及到两个核心概念:执行整个代码的线程咱们称之为主线程
,存放方法执行的地方咱们称之为执行栈
.dom
上面说完了同步过程,那这里来谈谈异步的过程。js引擎在遇到一个异步事件,不会一直等待返回结果而是将它挂起。当异步任务执行完以后会将结果加入到和执行栈中不一样的任务队列
当中,注意的是:此时放入队列不会当即执行其回调
,而是当主线程执行完执行栈中全部的任务以后再去队列中查找是否有任务,若是有则取出排在第一位的事件而后将回调放入执行栈并执行其代码。如此反复就构成了事件循环。异步
这里一样有一个核心概念:任务队列
socket
上面提到js执行异步方法的时候会将其返回结果放到队列中,这是比较笼统的,具体来讲,js会根据任务的类型将其放入不一样的队列,任务类型有两种:微任务、宏任务
。那么其对应的哪些是微任务、哪些是宏任务呢?函数
浏览器在执行的时候,先从宏任务队列中取出一个宏任务执行宏,而后在执行该宏任务下的全部的微任务,这是一个循环;而后再取出并执行下一个宏任务,再执行全部的微任务,这是第二个循环,以此类推.
注意:整个javascript代码是第一个宏任务
const process = require('process') setTimeout(function () {// 分发宏任务到EventQueue console.log("1"); }, 0); setTimeout(() => { console.log("11"); }, 0); setTimeout(() => { console.log("111"); }, 0); new Promise(function (resolve) { console.log('2'); resolve(); }).then(function () {// 发送微任务 console.log('3'); });
// 输出 2 3 1 11 111
在浏览器端,在咱们执行一片script的时候,当遇到同步代码,依次进入执行栈
,遇到异步代码,将其挂起,继续执行其它方法,当异步方法执行完以后根据任务类型进入到任务队列
,在执行栈执行完,主线程
空闲下来了以后会到任务队列中取任务回调并执行。
我本身认为Node的事件循环和浏览器端仍是有点区别的,它的事件循环依靠libuv引擎。
该图来自官网,这里展现了在node的事件循环的6个阶段。
对于咱们来讲咱们更关注 timer、poll、check这三个阶段便可。
poll 阶段有两个主要的功能:
poll 阶段的逻辑
若是event loop进入了 poll阶段,且代码未设定timer,将会发生下面状况:
b、若是poll queue为空,将会发生下面状况:
* 若是代码已经被setImmediate()设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的) * 若是代码没有设定setImmediate(callback),event loop将阻塞在该阶段等待callbacks加入poll queue;
若是event loop进入了 poll阶段,且代码设定了timer:
这两个函数的功能仍是相似的,不一样的是他们处于EventLoop的不一样阶段:timer、check。
setImmediate(()=>console.log("setInterval")); setTimeout(() => {console.log("setTimeout")},0);
上面两行代码会输出顺序是什么呢?其实两种可能都有.
1.当setTimeout的0ms并不能作到绝对0ms,若是已通过了timer阶段,那么此时setTimeout就会在下一次循环中执行,也就是说先setInterval、再setTimeout。
2.第二种可能就是正常流程了,先timer、再check
若是上面的代码再一个IO操做做呢?如:
require('fs').readFile(__filename,()=>{ setImmediate(()=>console.log("setInterval")); setTimeout(() => {console.log("setTimeout")}); })
此时只可能出现一种状况,先setInterval、再setTimeout,由于在io中已经执行过了timer(readFile时处于IO callback)。
下面一块儿来看以下代码:
setTimeout(() => { console.log("timer1") Promise.resolve().then(() => console.log("promise1")); process.nextTick(() => console.log("nextTick1")) }, 0); setTimeout(() => { console.log("timer2") Promise.resolve().then(() => console.log("promise2")); process.nextTick(() => console.log("nextTick2")) }, 0);
按照个人理解,它的输出应该是以下:先timer、而后切换阶段的时候执行微任务.
// 状况1 timer1 timer2 nextTick1 nextTick2 promise1 promise2
但是并非,它的输出一直是:
// 状况2 timer1 nextTick1 promise1 timer2 nextTick2 promise2
后台晚上查资料由于Node11对EventLoop做了修改,为了和浏览器兼容。因而呼我切换到10.8.0,发现上面两种状况都有(状况1比例大于状况2)。这点暂时还未查明什么缘由。
node中的6个阶段每一个阶段执行完都会伴随着执行微任务,同个MicroTask队列下process.tick()会优于Promise。
本篇主要介绍了浏览器和Node对于事件循环机制实现,因为能力水平有限,其中可能有误之处欢迎指出。
欢迎关注公众号: