资讯专栏INFORMATION COLUMN

理清浏览器下的事件循环机制(Event Loop)

nemo / 2909人阅读

摘要:何为事件循环机制的任务分两种,分别是同步任务和异步任务。如上图所示主线程在执行代码的时候,遇到异步任务进入并注册回调函数,有了运行结果后将它添加到事件队列中,然后继续执行下面的代码,直到同步代码执行完。

我们知道,JavaScript作为浏览器的脚本语言,起初是为了与用户交互和操作DOM,为了避免因为同时操作了同一DOM节点而引起冲突,被设计成为一种单线程语言。

而单线程语言最大的特性就是同一时间只能做一件事,这个任务未完成下一个任务就要等待,这样无疑是对资源的极大浪费,而且严重时会引起阻塞,造成用户体验极差。这个时候就引出了异步的概念,而异步的核心就是事件循环机制Event Loop。

何为事件循环机制?

JavaScript的任务分两种,分别是同步任务和异步任务。

同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务:不进入主线程而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程某个异步任务可以执行了,该任务才会进入主线程执行。

如上图所示:

主线程在执行代码的时候,遇到异步任务进入Event Table并注册回调函数,有了运行结果后将它添加到事件队列(callback queue)中,然后继续执行下面的代码,直到同步代码执行完。

主线程执行完同步代码后,读取callback queue中的任务,如果有可执行任务则进入主线程执行

不断重复以上步骤,就形成了事件循环(Event Loop)

结合上面步骤分析下这个例子:

1. 执行主线程同步任务,输出start【1】,继续往下执行
2. 遇到setTimeout,进入event table注册setTimeout回调,setTimeout回调执行完后,继续往下执行
3. 输出end【2】,同步任务执行完毕
4. 进入event queue,检查是否有可执行任务,取出event queue中setTimeout任务开始执行,输出setTimeout【3】

结果依次为:start -> end -> setTimeout

浏览器环境下的异步任务
在浏览器和node中的事件循环与执行机制是不同的,要注意区分,不要搞混。
执行过程

浏览器环境的异步任务分为宏任务(macroTask)和微任务(microtask),当满足条件时会分别被放进宏任务队列和微任务队列(先进先出),等待被执行。

微任务:
promise,MutationObserver

宏任务:
script整体,setTimeout & setIntervat,I/O,UI render。

执行过程如下:

如图所示:

1. 把整体的script代码作为宏任务执行
2. 执行过程中如果遇到宏任务和微任务,满足条件时分别添加至宏任务队列和微任务队列
3. 执行完一个宏任务后,取出所有微任务依次执行,如果微任务一直有新的被添加进来,则一直执行,直到把微任务队列清空
4. 不断重复2和3,直到所有任务被清空,结束执行。

分析:

第一轮:

输出start【1】,将setTimeout回调函数@1,放进宏任务队列;

将setTimeout回调函数@2,放进宏任务队列;

将setTimeout回调函数@3,放进宏任务队列;

执行new Promise函数输出promise4【2】,将Promise.then@1放进微任务队列;

输出end【3】,此时队列如下所示:

第一轮宏任务执行完毕,开始执行微任务,取出微任务Promise.then@1,输出promise5【4】,此时微任务队列被清空,开始第二轮执行。

第二轮:

取出宏任务setTimeout回调函数@1,输出timer1【5】,将回调函数中的Promise.then@2放进微任务队列;

宏任务setTimeout回调函数@1中无宏任务,开始执行微任务,取出Promise.then@2,输出promise1【6】,此时:

setTimeout回调函数@1中宏任务队列和微任务队列均被清空,开始第三轮执行

第三轮:

取出宏任务setTimeout回调函数@2,输出timer2【7】,将Promise.then@3放进微任务队列;

setTimeout回调函数@2中无宏任务,开始执行微任务,取出Promise.then@3,输出promise2【8】,此时:

宏任务setTimeout回调函数@2中宏任务队列和微任务队列均被清空,开始第四轮执行

第四轮:

取出宏任务setTimeout回调函数@3,输出timer3【9】,将Promise.then@4放进微任务队列;

setTimeout回调函数@3中无宏任务,开始执行微任务,取出Promise.then@4,输出promise3【10】

现在宏任务对列和微任务队列都被清空了,完成执行,结果为:start > promise4 > end > promise5 > timer1 > promise1 > timer2 > promise2 > timer3 > promise3

引入 async/await

asnyc知识点传送门

await表达式的运算结果取决于它右侧的结果

当遇到await时,会阻塞函数体内部处于await后面的代码,跳出去执行该函数外部的同步代码,当外部同步代码执行完毕,再回到该函数内部执行剩余的代码

补充aynsc的一点知识:

如果aynsc函数中return一个直接量,async 会把这个直接量通过Promise.resolve()封装成Promise对象,如果什么都没return,会被封装成Promise.resolve(undefined)

那么 引入了async await之后的执行过程是怎样的呢?

分析:

第一轮:

执行同步代码,输出:script start【1】,将setTimeout回调@1放入宏任务队列;

进入aynsc1函数中,执行同步代码输出:async1 start【2】,遇到await从右向左执行,进入async2函数,输出:async2【3】;aynsc2函数体中未返回任何东西等价于返回了Promise.resolve(undefined),拿到返回值后进入aynsc1函数体中,继续执行剩下的部分,这时候aynsc1中注释部分等价于:

async function async1() {
  console.log("async1 start");
  //await async2();
  //console.log("async1 end");
   await new Promise((resolve) => resolve()).then(resolve => {
     console.log("async1 end")
   })
}

将Promise.then@1推入到微任务队列;

继续执行同步代码,输出:promise1【4】,将Promise.then@2推入微任务队列

继续执行同步代码,输出:script end【5】,第一轮宏队列任务执行完毕,此时如下:

开始执行微任务,取出微任务Promise.then@1,值为undefined,这个时候Promise.then@1完成执行,则await aynsc2()得到了值也完成了执行,不再阻塞后面代码,那么执行同步代码输出:async1 end【6】;

取出微任务Promise.then@2,输出:promise2【7】,微任务全部执行完毕,现在开始第二轮执行

第二轮:

取出宏任务队列中的setTimeout@1,输出setTimeout【8】

所有任务队列均为空,结束执行,输出结果为:script start > async1 start > async2 > promise1 > script end > async1 end > promise2 > setTimeout

补充谷歌浏览器测试结果:

借用一个例子:await一个直接值的情况

分析:

第一轮:

执行同步函数,输出:1【1】,进入async1函数中,输出:2【2】,这个时候await虽然接收了一个直接值,但是还是要先执行外边的同步代码之后才能执行await后边的值

继续执行同步代码,输出:3【3】,进入Promise函数,输出:4【4】,将Promise.then推入微任务队列

同步代码执行完毕,进入 async1函数中输出:5【5】

宏任务执行完毕,进入微任务队列,开始执行微任务;取出Promise.then,输出:6【6】

任务队列为空,执行完毕,结果为: 1 > 2 > 3 > 4 > 5 > 6

再借个例子,这个有点复杂

分析:

第一轮:

将setTimeOut@1放入宏任务列队;

执行async1()函数体内的函数,输出:1【1】,遇到await,进入aynsc2函数体,输出:2【2】,将该函数体内promise.then@1放入微任务队列中;

执行New promise .. 输出3【3】,将该函数体内Promise.then@2放入微任务队列中,第一轮宏任务执行完毕,此时:

开始执行第一轮微任务,取出Promise.then@1,输出:4【4】,此时async2函数执行完毕,进入aynsc1函数,此时改动下aynsc1函数,等价于:

async function async1() {
  console.log("1")
  //const data = await async2()
  //console.log("6")
   const data = await new Promise(resolve => resolve("async2的结果")).then((resolve) => {
                    console.log(6); 
                    return resolve;
                })

   return data;
}

将上面promise.then@3推入微任务队列中,此时:

接着执行微任务,取出promise.then@2,输出:5【5】,取出promise.then@3,输出:6【6】,此时函数async1执行完成,接着执行async1().then(...),将async1().then@1推到微任务队列中,取出async1().then@1,输出:7【7】和 "async2的结果"【8】;

第一轮任务执行完毕,开始执行第二轮,此时:

第二轮:

开始执行第二轮宏任务,将setTimeOut@1取出执行,输出8【9】,完毕。

所以任务被执行完毕,结果为:1 > 2 > 3 > 4 > 5 > 6 > 7 > async2的结果 > 8

------------------------ END ----------------------------

PS: 好记性不如烂笔头,看了那么多资料,还是想总结一下,不然过一阵子就忘记了,如果辛苦指出哦,谢谢~

参考资料:

理解 JavaScript 的 async/await
浏览器和Node不同的事件循环(Event Loop)
Event Loop 原来是这么回事
这一次,彻底弄懂 JavaScript 执行机制
从event loop到async await来了解事件循环机制
...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102424.html

相关文章

  • 10分钟理解JS引擎的执行机制

    摘要:深入理解引擎的执行机制灵魂三问为什么是单线程的为什么需要异步单线程又是如何实现异步的呢中的中的说说首先请牢记点是单线程语言的是的执行机制。 深入理解JS引擎的执行机制 1.灵魂三问 : JS为什么是单线程的? 为什么需要异步? 单线程又是如何实现异步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.说说setTimeout 首先,请牢记2...

    zzbo 评论0 收藏0
  • JavaScript Event Loop 机制详解与 Vue.js 中实践应用

    摘要:机制详解与中实践应用归纳于笔者的现代开发语法基础与实践技巧系列文章。事件循环机制详解与实践应用是典型的单线程单并发语言,即表示在同一时间片内其只能执行单个任务或者部分代码片。 JavaScript Event Loop 机制详解与 Vue.js 中实践应用归纳于笔者的现代 JavaScript 开发:语法基础与实践技巧系列文章。本文依次介绍了函数调用栈、MacroTask 与 Micr...

    livem 评论0 收藏0
  • JavaScript 运行机制--Event Loop详解

    摘要:上代码代码可以看出,不仅函数比指定的回调函数先执行,而且函数也比先执行。这是因为后一个事件进入的时候,事件环可能处于不同的阶段导致结果的不确定。这是因为因为执行完后,程序设定了和,因此阶段不会被阻塞进而进入阶段先执行,后进入阶段执行。 JavaScript(简称JS)是前端的首要研究语言,要想真正理解JavaScript就绕不开他的运行机制--Event Loop(事件环) JS是一门...

    snifes 评论0 收藏0
  • 览器和Node中的事件循环机制

    摘要:二浏览器端在讲解事件循环之前先谈谈中同步代码异步代码的执行流程。三端我自己认为的事件循环和浏览器端还是有点区别的,它的事件循环依靠引擎。四总结本篇主要介绍了浏览器和对于事件循环机制实现,由于能力水平有限,其中可能有误之处欢迎指出。 一、前言 前几天听公司一个公司三年的前端说今天又学到了一个知识点-微任务、宏任务,我问他这是什么东西,由于在吃饭他浅浅的说了下,当时没太理解就私下学习整理一...

    KevinYan 评论0 收藏0
  • 览器下的 Event Loop

    摘要:前言是以单线程的形式运行在宿主环境下,采用了回调的形式来解决异步任务。线程中步就是在浏览器下的。 前言 javascript 是以单线程的形式运行在宿主环境下,javascript 采用了回调的形式来解决异步任务。 为什么是单线程? javascript 的最开始的出现是为了给 web 页面增添一些动态的效果,那么就避免不了获取页面上的元素信息,如果 javascript 是以多线程的...

    forrest23 评论0 收藏0

发表评论

0条评论

nemo

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<