【js进阶】全面理解Event Loop这一篇就够了
文章目录
- 一、前言
- 二、Event Loop知识铺垫
- 1、微任务(MircoTask)
- 2、宏任务(MacroTask/Task)
- 3、javascript runtime
- 三、浏览器环境的Event Loop
- 1、同步、异步 和 宏任务、微任务
- 2、简单实例分析
- 3、复杂案例分析
- 四、Node 环境下的 Event Loop
- 1、Event Loop的6阶段
- 2、process.nextTick()
- 3、实例分析
- 4、当堂小考
- 五、总结
一、前言
众所周知,在使用javascript时,经常需要考虑程序中存在异步的情况,如果对异步考虑不周,很容易在开发中出现技术错误和业务错误。作为一名合格的javascript使用者,了解异步的存在和运行机制十分重要且有必要;那么,异步究竟是何方神圣呢?我们不得不提Event Loop:也叫做事件循环,是指浏览器或Node环境的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是实现异步的原理。作为一种单线程语言,javascript本身是没有异步这一说法的,是由其宿主环境提供的
。
注意:Event Loop 并不是在 ECMAScript 标准中定义的,而是在 HTML 标准中定义的;
二、Event Loop知识铺垫
javascript
代码运行时,任务被分为两种,宏任务(MacroTask/Task)
和微任务(MircoTask)
;Event Loop
在执行和协调各种任务时也将任务队列分为Task Queue
和MircoTak Queue
分别对应管理宏任务(MacroTask/Task)
和微任务(MircoTask)
;作为队列,Task Queue
和MircoTak Queue
也具备队列特性:先进先出(FIFO—first in first out)
。
1、微任务(MircoTask)
在 HTML 标准中,并没有明确规定 Microtask,但是实际开发中包含以下四种:
- Promise中的
then、catch、finally
(原理参考:【js进阶】手撕Promise,一码一解析 包懂) - MutationObserver(监视 DOM 变动的API,详情参考MDN)
- Object.observe(废弃:监听标准对象的变化)
- Process.nextTick(Node环境,通常也被认为是微任务)
2、宏任务(MacroTask/Task)
基本上,我们将javascript中非微任务(MircoTask)
的所有任务都归为宏任务,比如:
- script中全部代码
- DOM操作
- 用户交互操作
- 所有的网路请求
- 定时器相关的 setTimeout、setInterval 等
- ···
3、javascript runtime
javascript runtime:为 JavaScript 提供一些对象或机制,使它能够与外界交互,是javascript的执行环境。
javascript执行时会创建一个main thread主线程
和call-stack 调用栈(执行栈,遵循后进先出的规则)
,所有的任务都会被放到调用栈/执行栈等待主线程执行
。其运行机制如下:
- 1)主线程自上而下依次执行所有代码;
- 2)同步任务直接进入到主线程被执行;
- 3)异步任务进入到
Event Table
,当异步任务有结果后,将相对应的回调函数进行注册,放入Event Queue
; - 4)主线程任务执行完空闲下来后,从
Event Queue(FIFO)
中读取任务,放入主线程执行; - 5)放入主线程的
Event Queue
任务继续从第一步开始,如此循环执行;
上述步骤执行过程就是我们所说的事件循环(Event Loop),上图展示了事件循环中的一个完整循环过程。
三、浏览器环境的Event Loop
不同的执行环境中,Event Loop的执行机制是不同的;例如Chrome 和 Node.js 都使用了 V8 Engine:V8 实现并提供了 ECMAScript 标准中的所有数据类型、操作符、对象和方法(注意并没有 DOM)。但它们的 Runtime 并不一样:Chrome 提供了 window、DOM,而 Node.js 则是 require、process 等等
。我们在了解浏览器中Event Loop的具体表现前需要先整理同步、异步、微任务、宏任务之间的关系!
1、同步、异步 和 宏任务、微任务
看到这里,可能会有很多疑惑:同步异步很好理解,宏任务微任务上面也进行了分类,但是当他们四个在一起后就感觉很混乱了,冥冥之中觉得同步异步和宏任务微任务有内在联系,但是他们之间有联系吗?又是什么联系呢?网上有的文章说宏任务就是同步的,微任务就是异步的 这种说法明显是错的!
其实我更愿意如此描述:宏任务和微任务是相对而言的,根据代码执时循环的先后,将代码执行分层理解,在每一层(一次)的事件循环中,首先整体代码块看作一个宏任务,宏任务中的 Promise(then、catch、finally)、MutationObserver、Process.nextTick就是该宏任务层的微任务;宏任务中的同步代码进入主线程中立即执行的,宏任务中的非微任务异步执行代码将作为下一次循环的宏任务时进入调用栈等待执行的;此时,调用栈中等待执行的队列分为两种,优先级较高先执行的本层循环微任务队列(MicroTask Queue),和优先级低的下层循环执行的宏任务队列(MacroTask Queue)!
注意:每一次/层循环,都是首先从宏任务开始,微任务结束;
2、简单实例分析
上面的描叙相对拗口,结合代码和图片分析理解:
答案暂时不给出,我们先进行代码分析:这是一个简单而典型的双层循环
的事件循环
执行案例,在这个循环中可以按照以下步骤进行分析:
- 1、首先区分出该层
宏任务
的范围(整个代码); - 2、区分
宏任务
中同步代码
和异步代码
同步代码:console.log('script start');
、console.log('enter promise');
和console.log('script end');
;
异步代码块:setTimeout
和Promise的then
(注意:Promise中只有then、catch、finally的执行需要等到结果,Promise传入的回调函数属于同步执行代码
); - 3、在
异步
中找出同层的微任务
(代码中的Promise的then
)和下层事件循环的宏任务
(代码中的setTimeout
) - 4、
宏任务
的同步代码优先进入主线程
,按照自上而下顺序执行完毕;
输出顺序为:
//同步代码执行输出
script start
enter promise
script end
- 5、当主线程空闲时,执行该层的
微任务
//同层微任务队列代码执行输出
promise then 1
promise then 2
- 6、首层事件循环结束,进入第二层事件循环(
setTimeout
包含的执行代码,只有一个同步代码)
//第二层宏任务队列代码执行输出
setTimeout
综合分析最终得出数据结果为:
//首层宏任务代码执行输出
script start
enter promise
script end
//首层微任务队列代码执行输出
promise then 1
promise then 2
//第二层宏任务队列代码执行输出
setTimeout
3、复杂案例分析
那么,你是否已经了解上述执行过程了呢?如果完全理解上述实例,说明你已经大概知道浏览器中Event Loop的执行机制,但是,要想知道自己是不是完全明白,不妨对于下列多循环的事件循环进行分析检验,给出你的结果:
console.log('1');setTimeout(function() {console.log('2');new Promise(function(resolve) {console.log('3');resolve();}).then(function() {console.log('4')})setTimeout(function() {console.log('5');new Promise(function(resolve) {console.log('6');resolve();}).then(function() {console.log('7')})})console.log('14');
})new Promise(function(resolve) {console.log('8');resolve();
}).then(function() {console.log('9')
})setTimeout(function() {console.log('10');new Promise(function(resolve) {console.log('11');resolve();}).then(function() {console.log('12')})
})
console.log('13')
分析:如下图草稿所示,左上角标a为宏任务队列,左上角标i为微任务队列
,同一层循环中,本层宏任务先执行,再执行微任务;本层宏任务中的非微任务异步代码块作为下层循环的宏任务进入下次循环,如此循环执行;
如果你的与下面的结果一致,恭喜你浏览器环境的Event Loop
你已经完全掌握,那么请开始下面的学习:
1->8->13->9->2->3->14->4->10->11->12->5->6->7
四、Node 环境下的 Event Loop
在Node
环境下,浏览器的EventLoop
机制并不适用,切记不能混为一谈。这里借用网上很多博客上的一句总结(其实我也是真不太懂):Node
中的Event Loop
是基于libuv实现的:libuv
是 Node
的新跨平台抽象层,libuv
使用异步,事件驱动的编程方式,核心是提供i/o
的事件循环和异步回调。libuv
的API
包含有时间,非阻塞的网络,异步文件操作,子进程等等。
1、Event Loop的6阶段
Node的Event loop一共分为6个阶段
,每个细节具体如下:
timers:
执行setTimeout和setInterval中到期的callback。pending callback:
上一轮循环中少数的callback会放在这一阶段执行。idle, prepare:
仅在内部使用。poll:
最重要的阶段,执行pending callback,在适当的情况下回阻塞在这个阶段。check:
执行setImmediate的callback。close callbacks:
执行close事件的callback,例如socket.on(‘close’[,fn])或者http.server.on('close, fn)。
注意:上面六个阶段都不包括process.nextTick()
重点:如上图所,在Node.js中,一次宏任务可以认为是包含上述6个阶段、微任务microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。
2、process.nextTick()
在第二节中就了解到,process.nextTick()
属于微任务,但是这里需要重点提及下:
process.nextTick()
虽然它是异步API的一部分,但未在图中显示。因为process.nextTick()
从技术上讲,它不是事件循环的一部分;- 当每个阶段完成后,如果存在 nextTick,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行(
可以理解为微任务中优先级最高的
)
3、实例分析
老规矩,线上代码:
console.log('1');setTimeout(function() {console.log('2');process.nextTick(function() {console.log('3');})new Promise(function(resolve) {console.log('4');resolve();}).then(function() {console.log('5')})
})
process.nextTick(function() {console.log('6');
})
new Promise(function(resolve) {console.log('7');resolve();
}).then(function() {console.log('8')
})setTimeout(function() {console.log('9');process.nextTick(function() {console.log('10');})new Promise(function(resolve) {console.log('11');resolve();}).then(function() {console.log('12')})
})
console.log('13')
将代码的执行分区进行解释
分析:如下图草稿所示,左上角标a为宏任务队列,左上角标i为微任务队列
,左上角标t为timers阶段队列
,左上角标p为nextTick队列
同一层循环中,本层宏任务先执行,再执行微任务;本层宏任务中的非微任务异步代码块作为下层循环的宏任务进入下次循环,如此循环执行:
- 1、
整体代码
可以看做宏任务,同步代码直接进入主线程执行,输出1,7,13
,接着执行同层微任务且nextTick优先执行输出6,8
; - 2、二层中宏任务中只存在
setTimeout
,两个setTimeout代码块依次进入6阶段中的timer阶段
以t1、t2
进入队列;代码等价于:
setTimeout(function() {console.log('2');process.nextTick(function() {console.log('3');})new Promise(function(resolve) {console.log('4');resolve();}).then(function() {console.log('5')})
})setTimeout(function() {console.log('9');process.nextTick(function() {console.log('10');})new Promise(function(resolve) {console.log('11');resolve();}).then(function() {console.log('12')})
})
- 3、
setTimeout
中的同步代码立即执行输出2,4,9,11
,nextTick
和Pormise.then
进入微任务执行输出3,10,5,12
; - 4、二层中不存在
6阶段中的其他阶段
,循环完毕,最终输出结果为:1->7->13->6->8->2->4->9->11->3->10->5->12
;
4、当堂小考
console.log('1');setTimeout(function() {console.log('2');process.nextTick(function() {console.log('3');})new Promise(function(resolve) {console.log('4');resolve();}).then(function() {console.log('5')setTimeout(function() {console.log('6');process.nextTick(function() {console.log('7');})new Promise(function(resolve) {console.log('8');resolve();}).then(function() {console.log('9')})})})
})
process.nextTick(function() {console.log('10');
})
new Promise(function(resolve) {console.log('11');resolve();
}).then(function() {console.log('12')setTimeout(function() {console.log('13');process.nextTick(function() {console.log('14');})new Promise(function(resolve) {console.log('15');resolve();}).then(function() {console.log('16')})})
})setTimeout(function() {console.log('17');process.nextTick(function() {console.log('18');})new Promise(function(resolve) {console.log('19');resolve();}).then(function() {console.log('20')})
})
console.log('21')
五、总结
浏览器
和Node
环境下,microtask 任务队列
的执行时机不同:Node 端,microtask 在事件循环的各个阶段之间执行;浏览器端,microtask 在事件循环的 macrotask 执行完之后执行;
欢迎大家关注本人的微信公众号,微信公众号将不定期发送相应学习文章和教程
微信号:chiyizao 或者微信公众号搜索: 迟亦早
【js进阶】全面理解Event Loop这一篇就够了相关推荐
- JS 进阶: 深入理解键盘事件 Keyboard Event
JS 进阶: 深入理解键盘事件 Keyboard Event 文章目录 JS 进阶: 深入理解键盘事件 Keyboard Event 正文 1. 基础 API 2. 基础事件:keydown.keyu ...
- JavaScript中事件循环的理解 Event Loop
为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop) 事件循环(Event Loop) 在JavaScript中,所有的任务都 ...
- 深入理解 Event Loop
众所周知,JavaScript(以下简称 JS) 是单线程语言,在 html5 中增加了 web workers,web workers 是新开了线程执行的,那么 JS 还是单线程的吗?当然是,为什么 ...
- 理解Python并发编程一篇就够了 - 线程篇
目录 前言 GIL 同步机制 1. Semaphore(信号量) 2. Lock(锁) 3. RLock(可重入锁) 4. Condition(条件) 5. Event 6. Queue 线程池 前言 ...
- Symbol 进阶底层详解(看这一篇就够了)
Symbol() 是在 ES6 中提出来的一个新的基础类型,它主要用来定义一个唯一的属性值,防止重复命名,再对象中有着重要的作用,请耐心看完,最下边有 Symbol 进阶,对初学者来说不太友好,若想深 ...
- 彻底理解Canal,看这篇就够了
目录 前言 1. 什么是canal 2. canal能做什么 3. 如何搭建canal 3.1 首先有一个MySQL服务器 3.2 安装canal 4. Java客户端操作 总结 前言 我们都知道一个 ...
- 拆分js文件_2021入门Webpack,看这篇就够了:Webpack.config.js 解析
这是优妈成长记的第63篇原创 这是一个webpack配置说明 本文是发布在github上webpack-demo的README文件内容.主要对webpack.config.js每一条的注释说明. gi ...
- sql语句进阶教程(学习sql这一篇就够了)
转载自:http://blog.csdn.net/u011001084/article/details/51318434 最近从图书馆借了本介绍SQL的书,打算复习一下基本语法,记录一下笔记,整理一下 ...
- 理解node.js中的 Event Loop
node中的 "event loop" 正是node能处理高并发的核心所在.也正是因为它,node虽然在本质上是个单线程,却能让大量的操作处于后台运行.这篇文章将详细说明 even ...
最新文章
- 重写Java Object类中的equals()方法
- [分享]我国食品行业ERP现状及分析解决方案
- Haproxy安装与配置
- Kubernetes - - k8s - v1.12.3 OpenLDAP统一认证
- 5求的值c语言编辑,C语言中怎样求1+3+5~~~~~+9值并 – 手机爱问
- 苹果暖场之后 华为P30系列正式发布!你的“望远镜”手机终于来了
- webpack打包原理_webpack打包原理入门探究(四)插件探究(上)
- 达内JAVA云笔记_达内云笔记项目完整代码+注释
- 新手学Java编程语言怎么入门?知识点都帮你整理好了
- java hsqldb_Hsqldb入门
- 软考中级软件设计师基础知识总结
- 提供2.4G单色、双色、RGB\RGBW\RGBCW调光LED灯方案
- 获取并处理中文维基百科语料
- 数位板软件测试工资,一位强迫症患者对板子的深度剖析,绘王H950P数位板测评...
- COGS 2482. Franky的胡子【二分,高精度】
- opcode加密php代码,总结Opcode缓存和PHP代码的加密
- xp计算机加域找不到网络路径,手把手为您win10系统计算机加域提示错误“找不到网络路径”的方法...
- 剑指offer----C语言版----第十一天
- 在线ARM仿真器知识(嵌入式系统设计师必备)
- Android安卓成品项目 购物商城系统源码apk