搜索
写经验 领红包
 > 情感

前端事件循环机制(事件循环是什么)

导语:高级前端进阶,你了解事件循环吗?

前言:

无论在工作中,还是在面试题中,event loop(事件循环)都十分重要,浏览器与nodejs中事件循环略有差异,本文只讨论浏览器中的事件循环,nodejs以后再单独写一篇。由于涉及到的点会比较多,可能显得比较啰嗦,请大家选择性观看,如果。

为什么需要事件循环

大家都知道JavaScript是单线程脚本语言,同一时间只能做一件事。而JavaScript中又存在异步http请求(ajax)、定时器、事件绑定等等的方法,如何检测异步请求是否完成、如何检测定时器时间、如何监听事件触发?这时候就需要浏览器的帮助了,浏览器是多线程的,这些异步或者事件触发亦或者定时器触发后浏览器会将相应的回调方法加入到任务队列中,等待JavaScript调用,而JavaScript循环的从任务队列中调用方法,我们就称之为事件循环。

浏览器有哪些线程GUI渲染线程负责渲染页面,例如解析html,css,布局并渲染页面js引擎线程最出名的就是我们熟知的V8引擎,解析并执行js当执行栈为空时会将任务队列头部的任务放入执行栈中执行事件触发线程事件绑定(鼠标点击、页面滚动等事件)会将任务注册到时间触发线程中,待相应的事件触发时会将相应的回调放入任务队列中定时器触发线程定时器计时,当定时器时间到时,会将回调函数放入任务队列。异步请求线程浏览器会新开一个线程进行请求,当检测到请求状态发生变化时,如果设置了回调函数,会将此函数放入任务队列中。JavaScript是如何执行的

JavaScript并不是一行一行的分析并执行代码的,而是分段一段一段的进行分析并执行。

这里要说明一下,怎么才算一段。这里的段指的是JavaScript中的执行上下文。

执行上下文分为三种全局执行上下文 这里指的就是全局代码函数执行上下文 这里指的是一个一个函数,一个函数执行前会先创建一个执行上下文eval执行上下文 eval不推荐使用,这里不做过多说明

由此可见当JavaScript运行时会有很多执行上下文,为了方便管理,js引擎会创建一个执行上下文栈来对这些执行上下文进行管理,遵循先进后出原则。

全局上下文在栈的最底部,全局上下文只有一个在浏览器关闭时出栈。

函数在执行时会创建函数执行上下文,并放入执行栈中执行,执行完毕后出栈。

event loop 循环机制

当JavaScript执行时,会将全局执行上下文放入执行栈中,接下来遇到函数执行上下文时会将这个上下文也放入执行栈中,执行完毕会出栈,当执行栈为空时,会从任务队列头部拿取一个任务,创建上下文并放入执行栈中执行。每当执行栈为空时总会循环的从任务队列获取任务,并创建执行上下文放入执行栈执行。这个循环我们称之为事件循环。

任务队列的分类

任务队列分为两种,一种叫宏任务(macrotask),一种叫微任务(microtask),这也是本文的重点。

接下来看看都有哪些属于宏任务,哪些属于微任务。

宏任务:script( 整体代码)、setTimeout、setInterval、I/O(http请求)、UI 渲染

微任务:Promise.then()、MutationObserver(监听dom的更改)、Object.observer(这玩意已经废弃了,可能有些文章还有提到这个)

new Promise(()=>{}),这个匿名函数内的代码数据同步代码,会立即执行

执行原则

执行完一个宏任务回去检测微任务队列是否为空,如果不为空则执行完队列内的所有微任务,如果队列为空,则继续执行下一个宏任务,下一个宏任务执行完后会继续检测微任务队列是否为空,往复循环。

接下来利用示例来加深理解

示例1:
console.log(1)setTimeout(()=>{console.log(2)})console.log(3)//输出//1//3//2
解析:打印1将setTimeout交由浏览器定时器线程,时间到了后会将回调函数放入宏任务队列打印3微任务队列为空,从宏任务队列取一个任务打印2示例2:
console.log(1)newPromise((resolve)=>{console.log(2)resolve()}).then(()=>{console.log(3)}).then(()=>{console.log(4)})setTimeout(()=>{console.log(5)})console.log(6)//输出//1//2//6//3//4//5
解析:打印1执行new Promise(),打印2将第一个then放入微任务队列将setTimeout交由定时器线程,时间到后将回调放入宏任务打印6同步任务执行完毕,查询微任务列表,从列表取出一个任务打印3,同时将第二个then放入微任务队列查询微任务队列,取出一个任务打印4查询微任务队列,发现为空,查询宏任务队列取出一个宏任务,打印5示例3:
console.log(1)newPromise((resolve)=>{①console.log(2)resolve()}).then(()=>{②console.log(3)}).then(()=>{③console.log(4)})newPromise((resolve)=>{④console.log(5)resolve()}).then(()=>{⑤console.log(6)}).then(()=>{⑥console.log(7)})setTimeout(()=>{⑦console.log(8)})console.log(9)//输出//1//2//5//9//3//6//4//7//8
解析:打印1执行①,打印2 ,将②放入微任务。当前微任务 [②]执行④,打印5,将⑤放入微任务。当前微任务[②,⑤]将setTimeout放入宏任务(此处略去定时器线程的操作,下同)。当前宏任务[⑦]打印9执行②,打印3,将③放入微任务。当前微任务[⑤,③]取出⑤,打印6,将⑥放入微任务。当前微任务[③,⑥]取出③,打印4,取出⑥,打印7微任务执行完毕,取出⑦打印8示例4:

接下来我们在示例中加入async、await

await后的方法会立即执行,await下面的本作用域代码会加入到微任务队列

console.log(1)newPromise((resolve)=>{①console.log(2)resolve()}).then(()=>{②console.log(3)}).then(()=>{③console.log(4)})test()setTimeout(()=>{④console.log(8)})console.log(9)asyncfunctiontest(){awaittest1();console.log(10)⑤}functiontest1(){console.log(11)}//输出//1//2//11//9//3//10//4//8
解析:打印1执行①内的代码,打印2将②放入微任务队列。微任务队列[②]执行test方法,进入test1,打印11将⑤放入微任务队列。微任务队列[②,⑤]将setTimeout放入宏任务队列。宏任务[④]打印9同步任务执行完毕,查询微任务队列取出②,打印3取出⑤,打印10,将③放入微任务队列。微任务队列[③]取出③,打印4微任务执行完毕,查询宏任务取出宏任务内的④,打印8示例5:
asyncfunctiontest1(){console.log(&39;);awaittest2();console.log(&39;);⑥}asyncfunctiontest2(){console.log(&39;);awaittest3()console.log(4)⑤}asyncfunctiontest3(){console.log(&39;);awaittest4()console.log(6)④}asyncfunctiontest4(){awaitconsole.log(&39;);console.log(8)①}console.log(9);setTimeout(function(){②console.log(10);},0)test1();newPromise(function(resolve){console.log(11);resolve();}).then(function(){console.log(12);③});console.log(13);//输出//9//1//3//5//7//11//13//8//12//6//4//2//10
解析:打印9执行test1,打印1进入test2, 打印3进入test3,打印5进入test4,打印7将①加入微任务队列。微任务队列[①]继续向下执行,将②加入宏任务队列。宏任务队列[②]执行new Promise,打印11,将③加入微任务队列。微任务队列[①,③]打印13同步任务结束,开始执行微任务队列,取出①打印8取出微任务③,打印12微任务执行完毕test4方法执行完毕,进行出栈,将④加入微任务队列。微任务队列[④]取出④,打印6test3出栈,将⑤加入微任务队列。微任务队列[⑤]取出⑤,打印4test2出栈,将⑥加入微任务队列。微任务队列[⑥]取出⑥,打印2,test1出栈微任务队列为空,执行宏任务取出②打印10结尾:

浏览器的事件循环,到这里基本结束了,如果大家觉得掌握的差不多了可以用下面的例子来测测自己对事件循环的理解程度。

示例6:
asyncfunctiontest1(){console.log(&39;);awaittest2();console.log(&39;);}asyncfunctiontest2(){console.log(&39;);awaittest3()console.log(4)}asyncfunctiontest3(){console.log(&39;);awaittest4()console.log(6)}asyncfunctiontest4(){awaitconsole.log(&39;);console.log(8)}console.log(9);setTimeout(function(){console.log(10);},0)test1();newPromise(function(resolve){console.log(11);resolve();}).then(function(){console.log(12);}).then(function(){console.log(14);}).then(function(){console.log(15);});console.log(13);

- END -

本文内容由快快网络小故整理编辑!