JavaScript引擎又称为JavaScript解释器,是JavaScript解释为机器码的工具,分别运行在浏览器和Node中。而根据上下文的不同,Event loop也有不同的实现:其中Node使用了libuv库来实现Event loop; 而在浏览器中,html规范定义了Event loop,具体的实现则交给不同的厂商去完成。浏览器中的Event Loops根据2017年新版的HTML规范HTML Standard,浏览器包含2类事件循环:browsing contexts 和 web workers。 browsing contexts中有一个或多个Task Queue,即MacroTask Queue,仅有一个Job Queue,即MicroTask Queue。macrotask queue(宏任务,不妨称为A)setTimeoutsetIntervalsetImmediate(node独有)requestAnimationFrameI/OUI renderingmicrotask queue(微任务,不妨称为I)process.nextTick(node独有)PromisesObject.observe(废弃)MutationObserver这两个任务队列执行顺序:取1个A中的task,执行之。把所有I顺序执行完,再取A中的下一个任务。为什么promise.then的回调比setTimeout先执行代码开始执行时,所有这些代码在A中,形成一个执行栈(execution context stack),取出来执行之。遇到setTimeout,则加到A中,遇到promise.then,则加到I中。等整个执行栈执行完,取I中的任务。(function test() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3);})()// 1// 2// 3// 5// 4//浏览器渲染步骤:Structure(构建 DOM) ->Layout(排版)->Paint(绘制) //新的异步任务将在下一次被执行,因此就不会存在阻塞。button.addEventListener(‘click’, () => { setTimeout(fn, 0)})V8源码 https://github.com/v8/v8/blob...https://github.com/v8/v8/blob...NodeJS中的Event Loop而在Node.js中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。node新加了一个微任务process.nextTick和一个宏任务setImmediate.process.nextTick在当前"执行栈"的尾部(下一次Event Loop之前)触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);});});setTimeout(function timeout() { console.log(‘TIMEOUT FIRED’);}, 0)// 1// 2// TIMEOUT FIREDsetImmediatesetImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。setImmediate(function A() { console.log(1); setImmediate(function B(){console.log(2);});});setTimeout(function timeout() { console.log(‘TIMEOUT FIRED’);}, 0);//不确定递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()process.nextTick(function foo() { process.nextTick(foo);});//FATAL ERROR: invalid table size Allocation failed - JavaScript heap out of memoryprocess.nextTick也会放入microtask quque,为什么优先级比promise.then高呢在Node中,_tickCallback在每一次执行完TaskQueue中的一个任务后被调用,而这个_tickCallback中实质上干了两件事:nextTickQueue中所有任务执行掉(长度最大1e4,Node版本v6.9.1)第一步执行完后执行_runMicrotasks函数,执行microtask中的部分(promise.then注册的回调)所以很明显process.nextTick > promise.then”node.js的特点是事件驱动,非阻塞单线程。当应用程序需要I/O操作的时候,线程并不会阻塞,而是把I/O操作交给底层库(LIBUV)。此时node线程会去处理其他任务,当底层库处理完I/O操作后,会将主动权交还给Node线程,所以Event Loop的用处是调度线程,例如:当底层库处理I/O操作后调度Node线程处理后续工作,所以虽然node是单线程,但是底层库处理操作依然是多线程。根据Node.js官方介绍,每次事件循环都包含了6个阶段,对应到 libuv 源码中的实现,如下图所示timers :这个阶段执行timer(setTimeout、setInterval)的回调I/O callbacks:执行一些系统调用错误,比如网络通信的错误回调idle, prepare :仅node内部使用poll :获取新的I/O事件, 适当的条件下node将阻塞在这里check :执行 setImmediate() 的回调close callbacks :执行 socket 的 close 事件回调timers阶段timers 是事件循环的第一个阶段,Node 会去检查有无已过期的timer,如果有则把它的回调压入timer的任务队列中等待执行,事实上,Node 并不能保证timer在预设时间到了就会立即执行,因为Node对timer的过期检查不一定靠谱,它会受机器上其它运行程序影响,或者那个时间点主线程不空闲。但是把它们放到一个I/O回调里面,就一定是 setImmediate() 先执行,因为poll阶段后面就是check阶段。I/O callbacks 阶段这个阶段主要执行一些系统操作带来的回调函数,如 TCP 错误,如果 TCP 尝试链接时出现 ECONNREFUSED 错误 ,一些 *nix 会把这个错误报告给 Node.js。而这个错误报告会先进入队列中,然后在 I/O callbacks 阶段执行。poll 阶段poll 阶段主要有2个功能:处理 poll 队列的事件当有已超时的 timer,执行它的回调函数even loop将同步执行poll队列里的回调,直到队列为空或执行的回调达到系统上限(上限具体多少未详),接下来even loop会去检查有无预设的setImmediate(),分两种情况:若有预设的setImmediate(), event loop将结束poll阶段进入check阶段,并执行check阶段的任务队列若没有预设的setImmediate(),event loop将阻塞在该阶段等待注意一个细节,没有setImmediate()会导致event loop阻塞在poll阶段,这样之前设置的timer岂不是执行不了了?所以咧,在poll阶段event loop会有一个检查机制,检查timer队列是否为空,如果timer队列非空,event loop就开始下一轮事件循环,即重新进入到timer阶段。check 阶段setImmediate()的回调会被加入check队列中, 从event loop的阶段图可以知道,check阶段的执行顺序在poll阶段之后。close 阶段突然结束的事件的回调函数会在这里触发,如果 socket.destroy(),那么 close 会被触发在这个阶段,也有可能通过 process.nextTick() 来触发。示例setTimeout(()=>{ console.log(’timer1’) Promise.resolve().then(function() { console.log(‘promise1’) })}, 0)setTimeout(()=>{ console.log(’timer2’) Promise.resolve().then(function() { console.log(‘promise2’) })}, 0)/浏览器中timer1promise1timer2promise2//node中timer1timer2promise1promise2/const fs = require(‘fs’)fs.readFile(’test.txt’, () => { console.log(‘readFile’) setTimeout(() => { console.log(’timeout’) }, 0) setImmediate(() => { console.log(‘immediate’) })})/readFileimmediatetimeout/更多示例libuv源码https://github.com/libuv/libu…其他requestAnimationFrameHTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()客户端可能实现了一个包含鼠标键盘事件的任务队列,还有其他的任务队列,而给鼠标键盘事件的任务队列更高优先级,例如75%的可能性执行它。这样就能保证流畅的交互性,而且别的任务也能执行到了。但是,同一个任务队列中的任务必须按先进先出的顺序执行。用户点击与button.click()的区别:用户点击:依次执行listener。浏览器并不实现知道有几个 listener,因此它发现一个执行一个,执行完了再看后面还有没有。click:同步执行listener。 click方法会先采集有哪些 listener,再依次触发。示例详情参考资料Promise的队列与setTimeout的队列有何关联?浏览器的 Event LoopEvent Loops深入理解js事件循环机制(Node.js篇)JavaScript 运行机制详解:再谈Event Loop