首页>>前端>>JavaScript->深度剖析JavaScript事件循环机制原理

深度剖析JavaScript事件循环机制原理

时间:2023-11-29 本站 点击:1

前言

拆解实现 Promise 及其周边文中大量聊到关于宏任务和微任务的知识点,其实这和事件循环机制息息相关。本文也将和大家一起来抠一抠事件循环机制的细节。

单线程语言

JavaScript是单线程语言,这点众所周知。那为啥JavaScript是单线程语言,从根本上改为多线程不好么?

阮一峰前辈文中提到原因,搬运一下:JavaScript从诞生起就是单线程。原因大概是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。后来就约定俗成,JavaScript为一种单线程语言。

简单来讲个场景:如果两个线程同时操作一个DOM,一个修改,一个删除,那以哪个为基准?为了避免这种场景,所以JS是单线程的。

H5提出的Web worker的标准,允许JavaScript创建多个线程,但子线程完全受主线程控制,所以,JavaScript本身依旧是单线程。

JavaScript单线程语言给我们造成了哪些问题呢? 举个单线程处理任务的?:

lethello='hello'letworld='world'console.log(hello+','+world)

上述代码,JS引擎编译完成之后,会把所有的任务代码放入主线程。等主线程开始执行,这些任务会按照顺序从上而下依次执行,至打印出hello,world后,主线程会自动退出。

一切都很美好~但现实是复杂且残酷的?‍♀️,不可能一直按部就班。如果单个任务执行时间过长导致后续任务阻塞,该怎么处理?

执行栈和任务队列

单线程就意味着,所有的任务需要排队。若前个任务执行时间过长,后一个任务就不得不一直等待,如IO线程(Ajax请求数据),不得不等待结果出来,再往下执行。

但这个等待是没有必要的,我们可以挂起等待中的任务,继续执行后续的任务。

因此,任务可分为两种:一种是同步任务;一种是异步任务。 同步任务:均在主线程上执行,用执行栈管理同步任务的进行。 异步任务:异步操作完成,先进入任务队列,等主线程执行栈空了,就去读取任务队列中的异步任务。

functionhelloWorld(){console.log('innerfunction')setTimeout(function(){console.log('executesetTimeout')})}helloWorld()console.log('outerfunction')

通过Loupe工具分析上述代码是否如我们所说的一样。

helloWorld函数先进入执行栈,开始执行helloWorld函数内的代码。

console.log('函数内')进入执行栈,打印函数内

执行setTimeout,属于定时任务,需要延迟等待,所以先挂起,后将匿名函数入队且继续执行主线程上的其余代码。

console.log('函数外')进入执行栈,打印函数外

主线程代码执行完毕,读取任务队列里的里的匿名函数,执行打印execute setTimeout

代码执行顺序与先前结论完美的契合~

事件循环

之所以称事件循环,是因为主线程从任务队列读取事件是循环不断的。为了更好地理解Event Loop转引自Philip Roberts的演讲《Help, I'm stuck in an event-loop》)

上图所示,主线程运行,会产生堆和栈,栈中的代码调用WebAPIs,当满足触发条件后,会将指定的回调函数或事件进行入队。当栈中代码执行完毕,就会循环读取任务队列里的事件,如此往复。

从图中还可以获取一个信息点:任务队列中的任务类型不仅只有一种,它包含了如输入事件(鼠标滚动、点击)、微任务、文件读写、WebSocket、定时器等等。其中如输入事件、文件读写、WebSocket都属于异步请求,等待I/O设备完成即可。而定时器是如何指定代码在规定时间之后进行?微任务又是什么?

定时器

定时器主要由setTimeoutsetInterval两个函数,两者类似,区别在执行次数,前者一次性执行,后者则反复执行。以setTimeout为例,基本用法如下。

functionhelloWorld(){console.log('helloworld')}lettimer=setTimeout(helloWorld,1000)

很简单,上述代码将通过setTimeout在1000ms后输出hello world。 不知道你有没有疑问?上文提到,推入任务队列中的任务都是按顺序读取执行,那么定时器的回调函数是如何保证在指定时间内被调用? 翻阅资料,发现Chromium中有关于设计延迟队列的概念,而延迟队列中的任务都是根据发起时间和延迟时间计算是否到期。若任务到期,则会先执行完成到期任务,再进行下一次循环。 使用定时器,还有一些注意事项? 若主线程任务执行时间过长,会影响定时器任务的执行。

functionhelloWorld(){console.log('helloworld')}functionmain(){setTimeout(helloWorld,0)for(leti=0;i<5000;i++){console.log(i)}}main()

如上代码,setTimeout函数虽设置了一个0延时的回调函数,但回调需在执行5000次循环后才可调用。查看Performance面板执行helloWorld将近延迟了400ms,如下图所示。

如果定时器存在嵌套调用,系统会设置最短时间间隔为4ms

functionhelloWorld(){setTimeout(helloWorld,0)}setTimeout(helloWorld,0)

Chrome中,定时器被嵌套调用5次以上,会判定当前方法阻塞,如果时间间隔小于4ms,会将每次间隔时间设置为4ms。如下图所示。

未激活页面,定时器执行最小间隔为1000ms 若标签页不是当前的激活标签,定时器最小时间间隔为1000ms,目的也是为了优化厚爱加载损耗及降低耗电量。

延迟页面时间最大值 ChromeSafariFirefox都是32bit存储延时值,所以最大只能存储2^31 - 1 = 2147483647(ms)31是因为二进制最高位是符号位,-1是因为有0的存在。

宏任务与微任务

了解微任务,那宏任务也得弄明白不是~。如下表,为宏任务与微任务相关技术。

宏任务微任务setTimeoutMutationObserver(html5)setIntervalprocess.nextTick(node)I/O、事件Promise.then/catch/finallysetImmediate(node)queueMicrotaskscript(整体代码块)requestAnimationFramepostMessage,MessageChannel

那宏任务与微任务在什么时候执行呢?

宏任务:新的任务添加到任务队列的尾部,当循环系统执行该任务的时候执行回调函数。 微任务:当前宏任务执行结束之前执行回调函数。

执行时机可以看出:每个宏任务都关联一个微任务队列。 执行顺序可以得出:先执行宏任务,然后执行当前宏任务下的微任务,若微任务产生新的微任务,则继续执行微任务,微任务执行完毕后,再继续下一轮宏任务的事件循环。

实践是检验真理的唯一标准,举个Promise的例子?

console.log('start')setTimeout(function(){//宏任务console.log('setTimeout')},0)letp=newPromise((resolve,reject)=>{console.log('初始化Promise')resolve()}).then(function(){console.log('内部Promise1')//微任务}).then(function(){console.log('内部Promise2')//微任务})p.then(function(){console.log('外部Promise1')//微任务})console.log('end')

script是宏任务,开始执行代码,打印start

遇到setTimeout宏任务,入任务队列,等待下一次事件循环。

遇到Promise立即执行,打印初始化Promise

遇到new Promise().then微任务,入script宏任务的微任务队列,等待当前宏任务完成。

遇到p.then微任务,入script宏任务的微任务队列,等待当前宏任务完成。

打印end,当前script宏任务执行完成。

查看当前script宏任务的微任务队列,队列不为空,取出当前队首new Promise().then,执行打印内部Promise1,再次碰到then微任务,则继续执行打印内部Promise2,执行完毕,出队。

script宏任务下的微任务队列不为空,继续取出p.then,执行打印外部Promise1,出队。

script宏任务下的微任务队列空了,开始执行下一个宏任务。

执行宏任务setTimeout打印setTimeout。检查任务队列已空,程序结束。

参考

JavaScript中的Event Loop(事件循环)机制 什么是Event Loop JavaScript 运行机制详解:再谈Event Loop

小工具

视频转GIF

作者:瑾行著作权归作者所有。

链接:https://juejin.cn/post/7000392227893542919


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/692.html