Event Loop 原来是这么回事
前沿
从前有座山,山里有座庙,庙里有个小和尚在讲故事、讲什么呢?讲的是:
从前有座山,山里有座庙,庙里有个小和尚在讲故事、讲什么呢?讲的是:
从前有座山,山里有座庙,庙里有个小和尚在讲故事、讲什么呢?讲的是:
...
请看一个小故事
以前有一个餐厅,这个餐厅有一个老板和一个厨师,自己创业的,刚开始起步阶段,没有资金请员工,所以自己来当老板兼服务员。
来这家餐厅的顾客有这四种类型
超级VIP客户
VIP客户
普通的客户
赖账的客户
作为VIP顾客,肯定得有VIP特权。
优先上菜
同等VIP,先点菜的先上菜
所以这个店的上菜顺序跟身份和点菜的顺序有关,
超级VIP客户>VIP客户>普通客户>赖账的客户
一天、老板开始营业后,陆陆续续的来了一些人进来点餐吃饭。
第一个进来的是普通的客户,点了一道回锅肉
第二个进来的是充了钱的VIP客户,点了一道小龙虾
第三个进来的是赖账的客户,点了很多菜
第四个进来的是一个超级VIP客户,点了一道酸菜鱼
由于这个店只有一个人,所以老板招待好他们点完餐之后就去炒菜了。根据上面提到的顺序,所以会先炒超级VIP客户点的菜、然后到VIP客户点的菜、然后到普通客户点的菜、最后到赖账的客户点的菜
让我们用伪代码看看如何实现这个逻辑
我们定义了四个function
superVipOrder(name, dish) 用来表示超级VIP用户下单点菜
vipOrder(name, dish) 用来表示VIP用户下单点菜
order(name, dish) 用来表示普通用户下单点菜
groupOrder(name, dish) 用来表示赖账的客户下单点菜
根据上面提到的上菜规则,
超级VIP客户>VIP客户>普通客户>赖账的客户
实际的上菜顺序我们可以知道是
那么问题来了,那些function都是什么呢?
其实很简单,这些function都对应着JavaScript中的一些异步函数
// 超级VIP客户// 微任务,将回调加入到 执行队列,优先执行function superVipOrder(name, dish, order) {process.nextTick(() => { console.log(red(`superVip顾客 ${name} 点了 ${dish} 已经上了`));});
}
// VIP客户// 微任务,将回调加入到 执行队列,优先执行,优先级比process.nextTick低function vipOrder(name, dish, order) { new Promise((resolve, reject) => {resolve(true);}).then(() => { console.log(blue(`vip顾客 ${name} 点了 ${dish} 已经上了`));});
}
// 普通客户下单// 宏任务,将回调加入到 宏任务的执行队列中function order(name, dish, order) {setTimeout(() => { console.log(yellow(`普通顾客 ${name} 点了 ${dish} 已经上了`));}, 0);
}
// 赖账的客户下单function groupOrder(name, dish, order) {setImmediate(() => { console.log(green(`赖账的顾客 ${name} 点了 ${dish} 已经上了`));});
}
我们可以暂且先把process.nextTick
认为是超级vip用户,优先级最高、
原生Promise
认为是vip用户,执行优先级高
setTimeout
认为是普通用户,执行优先级一般
setImmediate
认为是赖账的顾客,执行优先级低
还原伪代码
我们将伪代码还原成这些异步函数,这会让我们看的更加直观、亲切一些
根据上面故事提到的优先级规则,我们知道输出的结果是这样的
为什么会是这样的结果呢?下面就来讲讲JavaScript中的Event Loop
Event Loop
1. JavaScript的事件循环
我们知道JavaScript
是单线程的,就像上面故事的老板,他得去服务员去招待客人点菜,并将菜单给厨师,厨师炒好后再给到他去上菜。如果老板不请个厨师,自己来炒菜的话,那么在炒菜时就没办法接待客人,客人就会等待点菜。等着等着就会暴露出服务态度不行的问题。所以说,得有厨师专门处理炒菜的任务
所以在js中,任务分为同步任务和异步任务,
同步任务 -> 服务员去接待客人点菜
异步任务 -> 厨师炒菜、异步回调函数相当于 服务员去上菜
JS的事件循环如图所示,
在执行主线程的任务时,如果有异步任务,会进入到Event Table并注册回调函数,当指定的事情完成后,会将这个回调函数放到callback queue中
在主线程执行完毕之后,会去读取callback queue中的回调函数,进入主线程执行
不断的重复这个过程,也就是常说的Event Loop(事件循环)了
2. 异步任务
异步任务又分为宏任务跟微任务、他们之间的区别主要是执行顺序的不同。
在js中,微任务有
原生的Promise -> 其实就是我们上面提到的VIP用户,
process.nextTick -> 其实就是我们上面提到的超级VIP用户,
process.nextTick的执行优先级高于Promise的
宏任务
整体代码 script
setTimeout -> 其实就是我们上面提到的普通用户,
setImmediate -> 其实就是我们上面提到的群体用户,
setTimeout的执行优先级高于setImmediate的
宏任务与微任务的执行过程
在一次事件循环中,JS会首先执行 整体代码script,执行完后会去判断微任务队列中是否有微任务,如果有,将它们逐一执行完后在一次执行宏任务。如此流程
测试
下面我们来看一段代码是否了解了这个流程
执行结果为:b d e f a h c g
让我们来分析一下这段代码的执行流程
首页执行第一个宏任务 整段
script
标签代码,遇到第一个setTimeout
,将其回调函数加入到宏任务队列中,输出
console.log('b')
遇到process.nextTick,将其回调函数加入到微任务
遇到setImmediate 将其回调函数加入到宏任务队列中
宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout | process.nextTick |
setImmediate |
当第一个宏任务执行完后,就会去判断是否还有微任务,刚好有一个微任务,执行process.nextTick的回调,输出
console.log('d')
,然后又遇到了一个process.nextTick,又将其放入到微任务队列继续将微任务队列中的回调函数取出,继续执行,输出
console.log('e')
,然后又遇到了一个process.nextTick,又将其放入到微任务队列继续将微任务队列中的回调函数取出,继续执行,输出
console.log('f')
,然后又遇到了一个process.nextTick,又将其放入到微任务队列
宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout | |
setImmediate |
当微任务队列为空后,开始新的宏任务,取出第一个宏任务队列的函数,
setTimeout
,执行console.log('a')
,然后遇到Promise
,process.nextTick
将其回调加入到微任务队列。执行完后
宏任务Event Queue | 微任务Event Queue |
---|---|
setImmediate | promise.then |
- | process.nextTick |
继续判断微任务队列是否有回调函数可执行,由于
process.nextTick
的执行优先级大于promise
,所以会先执行process.nextTick
的回调,输出console.log('h');
、如果有多个process.nextTick
的回调,会将process.nextTick
的所有回调执行完成后才会去执行其它微任务的回调。 当nextTick所有的回调执行完后,执行promise
的回调,输出console.log('c');
,直到promise的回调队列执行完后,又会去判断是否还有微任务。
宏任务Event Queue | 微任务Event Queue |
---|---|
setImmediate |
微任务执行完后,开始执行新的宏任务,执行
setImmediate
的回调,输出console.log('g');
3. setImmediate
这里为什么要把setImmediate
单独拿出来说呢,因为它属于宏任务的范畴,但又有点不一样的地方。
先看一段代码
按照我们上面的分析逻辑,我们会认为这段代码的输出结果应该是a b c d
。 如果我们把使用Node 0.10.x的版本去执行这段代码,结果确实是输出a b c d
然而,在Node 大于 4.x 的版本后,在执行setImmediate
的,会使用while循环,把所有的immediate回调取出来依次进行处理。
ps:在 node 11版本以上,已经把setImmediate按照宏任务的执行顺序进行处理了,不会循环依次进行调用了
最后看一段代码看看自己是否真的掌握了
如果还没有掌握,欢迎评论区吐槽
输出的结果为:start end a e g f h b d c i
简单分析一下代码:
第一轮事件循环开始,执行
script
代码,输出start
end
,将process.nextTick
的回调加入微任务队列中,将setImmediate
的回调加入到宏任务的队列中执行微任务队列中的
process.nextTick
的回调,输出a
、将setImmediate
的回调加入到宏任务的队列中,遇到promise
、将回调加入到微任务队列中。
宏任务 | 微任务 |
---|---|
setImmediate | promise.then |
setImmediate | - |
继续执行微任务队列中的回调,取出
promise.then
并执行,输出e
,将process.nextTick
的回调放入到微任务中,遇到promise
、将回调加入到微任务队列中。判断当前promise的回调队列是否还有回调函数没执行,如果有,将继续执行,取出刚刚放入的promise的回调,输出
g
,当Promise回调队列执行完后,继续判断当前是否还有微任务。取出
process.nextTick
的回调并执行,输出f
宏任务 | 微任务 |
---|---|
setImmediate | - |
setImmediate | - |
setTimeout | - |
当前微任务队列为空后,开始执行宏任务,因为
setTimeout
的优先级大于setImmediate
,所以先取出setTimeout
的回调并执行,输出h
当前微任务队列还是为空,开始执行宏任务,取出所有
setImmediate
的回调函数,并执行,输出b d
,将process.next
与promise
的回调放入到微任务队列中。取出微任务队列中的回调函数,并执行,输出
c i
总结
Event Loop 作为面试的高频题,静下心来认真的分析一下,其实不难理解。
原文链接:https://ccode.live/lentoo/list/3
Event Loop 原来是这么回事相关推荐
- 你不知道的Event Loop
关于Event Loop网上有很多文章都有讲解,包括我自己也有几篇文章有讲述event loop相关内容.之前写了一篇文章总结了Nodejs中event loop的原理,这里的event loop指的 ...
- 【转载】浏览器事件循环机制(event loop)
首先,本文转自https://juejin.im/post/5afbc62151882542af04112d 当我看完菲利普·罗伯茨的 javascript event loop的演讲的时候,就对于事 ...
- 简单理解浏览器的event loop 和 JavaScript的同步异步
为什么JavaScript是单线程的? JavaScript的主要用途是和用户进行交互以及对DOM的操作,为了避免复杂的同步问题(如果多线程,A线程对某DOM添加内容,B线程对它又进行了删除操作,这往 ...
- anaconda spyder使用协程报错解决:RuntimeError: This event loop is already running
早上在anaconda的spyder中写协程代码时遇到了报错. 代码如下: import asyncioasync def coroutine():print("hey")awai ...
- 从Promise来看JavaScript中的Event Loop、Tasks和Microtasks
原文 github.com/creeperyang- 主题 Promise 看到过下面这样一道题: (function test() {setTimeout(function() {console.l ...
- 跟着 Event loop 规范理解浏览器中的异步机制
原文发自我的 GitHub blog,欢迎关注 前言 我们都知道 JavaScript 是一门单线程语言,这意味着同一事件只能执行一个任务,结束了才能去执行下一个.如果前面的任务没有执行完,后面的任务 ...
- JavaScipt 中的事件循环(event loop),以及微任务 和宏任务的概念
说事件循环(event loop)之前先要搞清楚几个问题. 1. js为什么是单线程的? 试想一下,如果js不是单线程的,同时有两个方法作用dom,一个删除,一个修改,那么这时候浏览器该听谁的? 2. ...
- js异步等待完成后再进行下一步操作_彻底搞懂JS事件中的循环机制 Event Loop
我们都知道JavaScript是单线程语言,就是因为单线程的特性,就不得不提js中的同步和异步 一.同步和异步 所谓单线程,无非就是同步队列和异步队列,js代码是自上向下执行的,在主线程中立即执行的就 ...
- JavaScript 运行机制详解:Event Loop
转自: http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单 ...
最新文章
- 制定规则者与打破规则者
- linux挂载一个文件夹,linux挂载一个文件夹到另一个文件夹
- 复制出来的文本都是大写_好用又冷门的Word快捷键,据说80%的人都不知道!
- 如何处理SAP Fiori gateway service使用过程中遇到的404 error
- [react] react多个setState调用的原理是什么?
- 前端学习(2153):Vue的终极解决方案
- 批处理call和start
- linux内核工程导论,Linux内核工程导论——内存管理(3)
- Java——标准异常
- plc 上位机编译算法_基于西门子PLC的Socket通信深度剖析
- 连接设备不支持android,安卓手机不识别U盘、不能连接PC的处理方法
- JAVA生成条码(jbarcode)
- Linux的学习之路grep命令
- 正则表达式驼峰标示转下划线
- implicit declaration of function —— 函数隐式声明 警告
- 非常详细的图文安装wordpress安装教程
- 定时计数程序c语言,MCS-51系列单片机C语言编程定时/计数器程序模板
- 端对端加密通讯协议Signal protocol 学习(转)
- java unicode 转换_Java unicode中文编码转换和反转
- AI遮天传 ML/DL-感知机
热门文章
- 关闭windows安全警报_关闭 Windows 10 系统中自带 Defender 防病毒软件三种方法
- 一.关于itoa,atoi添加头文件后仍报错,编写相应自定义函数解决方案。
- 面向自动驾驶的高精度地图
- 数字孪生技术结合大数据创建智慧城市体系架构
- sql上一个月和下一个月
- 新手必读:三层交换技术基础知识详解
- IPSEC点到多点(SA+NAT穿越)策略模板方式成功配置
- mac php.ini 配置,Mac上PHP的配置 | Soo Smart!
- sqldeveloper导出数据字典_如何全面建设B端产品中的数据迁移方案
- 存储基础知识及硬盘挂载