浏览器和NodeJS的EventLoop的异同,以及一些机制PS:有些人对promise部分比较迷惑。Promise本身的构造函数是同步的,.then是异步的。----2018/7/622:35修改后的javascript是一种单线程脚本语言。虽然是单线程,但是有很多异步API可以帮助开发者解决线程阻塞问题。比如:onClick注册的回调函数,必不可少的ajax等等……但是javascript运行环境如何实现单线程,而不是一直阻塞线程,等待各种异步操作完成再继续执行操作呢?答案是:事件循环1。HTML5中规定了事件循环的规范。2.事件循环是javascript运行环境的机制(手动加粗)。3、浏览器实现的事件循环和NodeJS实现的事件循环有相同点也有不同点。HTML5中定义的事件循环规范链接https://www.w3.org/TR/html5/w...一个浏览器的事件循环1.简单理解事件循环就是事件循环。它的结构是什么?阮一峰老师的博客里有一张图。虽然非常直白清晰,但还是有些缺失,无法完整展示事件循环的整体循环机制。先看图:图非作者原创,来自阮一峰博客,在此说明,侵删。从图中我们可以得到以下信息:1.javascript引擎对javascript的执行是单线程的,因为只有一个栈,里面有各种正在执行和等待执行的事件。2、有些webAPI把执行过程中产生的回调放到一个队列中,即“事件队列”。3、在事件循环中,将“事件队列”中等待执行的事件源源不断地推入javascript执行栈。这就是事件循环简化的机制,为什么要简化?因为循环中还有很多操作和规则没有提到。我就不举栗子了,我打个比方。就说一个老生常谈的问题(编辑文章不方便,就一行,你要来揍我!)setTimeout(e=>{console.log(1)},0);newPromise((res,rej)=>{res()}).then(e=>{console.log(2)});同样是javascript中提供的异步API,也是直接执行的(开发者所希望的,虽然会因为Causedelay而被阻塞,防止barfine),但是无论是谁上传或者下载这两行代码,输出都会be21.因为这涉及到事件循环中宏任务和微任务的执行顺序和规则。2.整体流程回到刚才流程图不够完善的问题,这里放一张完整全面的事件循环流程图。图片非作者原创,来自javascript忍者秘笈,在此说明,侵删。这是事件循环的完整流程图。从图中我们可以看到很多刚才没有提到的术语,从头到尾(从上到下)整理一下:1.读取Macrotask队列中的任务。任务队列为空和向下执行任务队列不为空两种情况。将第一个进入的任务(手动+文章加粗)压入javascript执行栈,向下执行2.读取Microtask队列中的任务。任务队列为空和向下执行任务队列不为空有两种情况。将第一个进入的任务压入javascript执行栈,再次重复这个操作(手动+文章加粗),直到Microtask队列为空。说白了:把所有的任务依次压入这个任务队列中的javascript执行栈,向下执行3.根据本次循环的耗时(手册+文章加粗),判断是否需要,UI是否可以更新【后面再说】提一下这个循环时间问题】不需要,重复第一步,向下执行4.更新UI,UI渲染,同时阻塞javascript执行。并继续重复第一步。以上就是一个完整的事件循环过程。从流程中我们可以看出有两个“任务队列”。将这两个队列实例化成javascript的API是Macrotaskqueue-->setTimeout||设置间隔||javascriptcodeMicrotaskqueue-->Promise.then()至此,一个完整的事件循环过程就完全走完了。3、实例分析是什么鬼?太复杂了?理解?不存在现在回到刚才提到的“常见问题”,从例子的角度来解释问题。我们假设这个javascript文件叫做“main.js”“main.js”中的代码(+是自定义标记)+1console.log(1);+2setTimeout(e=>{console.log(2);},0)+3setTimeout(e=>{console.log(3);},0)+4newPromise((resolve,reject)=>{console.log(4);resolve();}).然后(e=>{console.log(5);})+5setTimeout(e=>{console.log(6);+6newPromise((resolve,reject)=>{console.log(7);resolve();}).then(e=>{console.log(8);})})那么执行顺序是怎样的呢?从头到尾梳理一下(词穷,只要统一整个流程,就是“从头到尾走一遍”)macrotask:javascript代码,全部同步代码执行。输出:14.将+4注册到microtask。注册+2+3+5到macrotask。微任务:执行+4输出:5。宏任务:执行+2。输出2.微任务:无宏任务:执行+3。输出3.microtask:nomacrotask:execute+5.输出67.将+6注册到microtask。微任务:输出8。所以整体输出的顺序是:14523678如果输出是你想的那样,那基本就没有问题了。那么,万一出了什么问题或出了什么问题呢?PS:前面提到【本次循环的耗时】,这里不是很清楚,希望大牛指点。浏览器一般以60/S渲染页面,达到每秒60帧(60fps),所以大约需要16ms。既然有时间,我们就问?上一个任务处理延迟了怎么办?因为javascript线程和UI线程是互斥的,有些任务导致javascript引擎坑队友,在16ms的节点上自然到不了这一步。我从javascriptninja的秘密中了解到,这种渲染一般都会被放弃,等待下一个时间周期。(有问题请指正!)浏览器中的事件循环到此结束。下面说说NodeJS的事件循环。NodeJS的事件循环也有Macrotask队列和Microtask队列。只是NodeJS的略有不同。那么让我们来谈谈区别。NodeJS中的Macrotaskqueue和Microtaskqueue实例化到API为:Macrotaskqueue-->script(主程序代码),setImmediate,I/O,setTimeout,setIntervalMicrotaskqueue-->process.nextTick,Promise1.Macrotaskqueue的区别上面说到浏览器事件循环的Macrotask队列,每个循环只会读取一个task,而NodeJS中的Macrotask队列是一次性读取的。,并阅读Microtask。注意:本文与NodeJS版本有很大关系。在看《浅显NodeJS》这本书的时候(我看的版本很老,不知道有没有修订版,有的话请告知),提到的setImmediate只会执行一次,而v8.9.1版本运行时给出的例子与书上写的不符。书上的例子如下(+是自定义标记,原文没有):+1process.nextTick(function(){console.log('nextTickexecution1');});+2process.nextTick(function(){console.log('nextTick执行2');});+3setImmediate(function(){console.log('setImmediate?执行1');+4process.nextTick(function(){console.log('强插入');});});+5setImmediate(function(){console.log('setImmediate?execution2');});+6console.log('正常执行');正常执行nextTick执行1nextTick执行2setImmediate执行1强制插入setImmediate?执行2.v8.9.1中的截图如下。从图中可以看出,至少在v8.9.1中,Macrotask队列会被直接执行。照例从头到尾整理一下:macrotask:javascript代码,全部是同步代码执行。输出:正常执行。向Macrotask注册+3+5。执行process.nextTick(),最终输出:正常执行,nextTick执行1,nextTick执行2。**microtask:无macrotask:执行+3+5。输出:setImmediate执行1,setImmediate?执行2。执行process.nextTick(),最终输出:setImmediate执行1,setImmediate?执行2,强插入。microtask:None,所以最后的输出是:正常执行,nextTick执行1次,nextTick执行2次,setImmediate?执行2次,强插入。2、process.nextTick()、setImmediates、事件循环的6个阶段NodeJS中Macrotask队列会分为6个阶段,每个阶段的作用如下(process.nextTick()会在最后执行6个阶段中的一个):计时器:在setTimeout()和setInterval()中执行过期回调。I/O回调:上一个周期的少量I/O回调会延迟到本轮这个阶段执行idle,prepare:只内部使用poll:最重要的阶段,执行I/O回调,在适当的情况下,会阻塞在这个阶段check:ExecutecallbackofsetImmediateclosecallbacks:执行close事件的回调,比如socket.on("close",func)注:这6个stage不是作者独创的作者来自https://cnodejs.org/topic/5a9...,文章从底层C代码分析NodeJS事件循环。这里我们只做简单的整合。入侵和删除。了解了这六个阶段后,我们可以发现在NodeJS事件循环中,定时器序列在Macrotask队列中的读取顺序是:1.setTimeout(fun,0)setInterval(fun,0)2.setImmediate为空,在例子明白。下面是代码(代码较长,分为三段,方便阅读,避免滚动。):+1process.nextTick(function(){console.log("1");});+2process.nextTick(function(){console.log("2");+3setImmediate(function(){console.log("3");});+4process.nextTick(function(){console.log("4");});});+5setImmediate(function(){console.log("5");+6process.nextTick(function(){console.log("6");});+7setImmediate(function(){console.log("7");});});+8setTimeout(e=>{console.log(8);+9newPromise((resolve,reject)=>{console.log(8+"promise");resolve();}).then(e=>{console.log(8+"promise+then");})},0)+10setTimeout(e=>{console.log(9);},0)+11setImmediate(function(){console.日志(“10”);+12process.nextTick(函数(){console.log(“11”);});+13process.nextTick(function(){console.log(“12”);});+14setImmediate(function(){console.log("13");});});console.log("14");+15newPromise((resolve,reject)=>{console.log(15);resolve();}).then(e=>{console.log(16);})这么复杂的异步嵌套在一起是不是很头疼?我!不!看!向上!最后一次梳理,自古以来最大最全的梳理,从头到尾梳理macrotask:javascript代码,全部同步代码执行。输出:14.执行process.nextTick(),最终输出:14,15,1,2,4.注册+3+5+8+11到Macrotask。向Microtask注册+15。microtask:execute+15output16macrotask:execute+8+10output8,8promise,9.注册+9到Microtask。microtask:执行+9输出8promise+thenmacrotask:执行+5+11+3输出5、10、3。注册+7+14到macrotask。执行process.nextTick(),最终输出:510361112。microtask:无macrotask:执行+7+14。Output:7,13microtask:None它们全部的输出是:14,15,1,2,4,8,8promise,9,8promise+then,5,10,3,6,11,12,7,13三结束这是结束。浏览器和NodeJS的事件循环已经完整的分析完了。过程中引用:阮一峰博客、知乎、CSDN文章部分内容,侵删。最近在理解一些底层知识方面收获颇丰。包括forof...等奇奇怪怪的问题,有时间再写吧。最后,本人菜鸟,如有错误或不正确、不真实、误导等问题,欢迎在评论区指正。
