用大白话告诉你什么是Event Loop
文章原文地址
前沿
从前有座山,山里有座庙,庙里有个小和尚在讲故事、讲什么呢?讲的是:
从前有座山,山里有座庙,庙里有个小和尚在讲故事、讲什么呢?讲的是:
从前有座山,山里有座庙,庙里有个小和尚在讲故事、讲什么呢?讲的是:
...
请看一个小故事
以前有一个餐厅,这个餐厅有一个老板和一个厨师,自己创业的,刚开始起步阶段,没有资金请员工,所以自己来当老板兼服务员。
由于刚开业,所以会有一个充值优惠的活动,充了1000元是超级VIP客户,充了100元以上的是VIP客户,
所以来这家餐厅的顾客有这四种类型
- 家里有矿的超级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,执行完后会去判断微任务队列中是否有微任务,如果有,将它们逐一执行完后在一次执行宏任务。如此流程
测试
下面我们来看一段代码是否了解了这个流程
<script>
setTimeout(() => {
console.log('a');
new Promise( res => {
res()
}).then( () => {
console.log('c');
})
process.nextTick(() => {
console.log('h');
})
}, 0)
console.log('b');
process.nextTick( () => {
console.log('d');
process.nextTick(() => {
console.log('e');
process.nextTick(() => {
console.log('f');
})
})
})
setImmediate( () => {
console.log('g');
})
</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回调取出来依次进行处理。
这也是我为什么把 setImmediate 比喻成 一桌子人客户的原因。
最后看一段代码看看自己是否真的掌握了
如果还没有掌握,欢迎评论区吐槽
<script>
console.log("start");
process.nextTick(() => {
console.log("a");
setImmediate(() => {
console.log("d");
});
new Promise(res => res()).then(() => {
console.log("e");
process.nextTick(() => {
console.log("f");
});
new Promise(r => {
r()
})
.then(() => {
console.log("g");
});
setTimeout(() => {
console.log("h");
});
});
});
setImmediate(() => {
console.log("b");
process.nextTick(() => {
console.log("c");
});
new Promise(res => res()).then(() => {
console.log("i");
});
});
console.log("end");
</script>
输出的结果为: start end a e g f h b d c i
简单分析一下代码:
- 第一轮事件循环开始,执行
script代码,输出startend,将process.nextTick的回调加入微任务队列中,将setImmediate的回调加入到宏任务的队列中 - 执行微任务队列中的
process.nextTick的回调,输出a、将setImmediate的回调加入到宏任务的队列中,遇到promise、将回调加入到微任务队列中。
| 宏任务 | 微任务 |
|---|---|
| setImmediate | promise.then |
| setImmediate | - |
- 继续执行微任务队列中的回调,取出
promise.then并执行,输出e,将process.nextTick的回调放入到微任务中,遇到promise、将回调加入到微任务队列中。 - 判断当前promise的回调队列是否还有回调函数没执行,如果有,将继续执行,取出刚刚放入的promise的回调,输出
g,当Promise回调队列执行完后,继续判断当前是否还有微任务。 - 取出
process.nextTick的回调并执行,输出g
| 宏任务 | 微任务 |
|---|---|
| setImmediate | - |
| setImmediate | - |
| setTimeout | - |
- 当前微任务队列为空后,开始执行宏任务,因为
setTimeout的优先级大于setImmediate,所以先取出setTimeout的回调并执行,输出h - 当前微任务队列还是为空,开始执行宏任务,取出所有
setImmediate的回调函数,并执行,输出b d,将process.next与promise的回调放入到微任务队列中。 - 取出微任务队列中的回调函数,并执行,输出
c i
总结
Event Loop 作为面试的高频题,静下心来认真的分析一下,其实不难理解。
欢迎关注
欢迎关注公众号“码上开发”,每天分享最新技术资讯
用大白话告诉你什么是Event Loop的更多相关文章
- 用大白话告诉你 :Java 后端到底是在做什么?
阅读本文大概需要 6 分钟. 作者:黄小斜 新手程序员通常会走入一个误区,就是认为学习了一门语言,就可以称为是某某语言工程师了.但事实上真的是这样吗?其实并非如此. 今天我们就来聊一聊,Java 开发 ...
- 一文告诉你 Event Loop 是什么?
Event Loop 也叫做"事件循环",它其实与 JavaScript 的运行机制有关. JS初始设计 JavaScript 在设计之初便是单线程,程序运行时,只有一个线程存在, ...
- [译]Node.js - Event Loop
介绍 在读这篇博客之前,我强列建议先阅读我的前两篇文章: Getting Started With Node.js Node.js - Modules 在这篇文章中,我们将学习 Node.js 中的事 ...
- 对Node.JS的事件轮询(Event Loop)的理解
title: Node.JS的事件轮询(event loop)的理解 categories: 理解 tags: Node JS 机制 当我们知道I/O操作和创建新线程的开销是巨大的! 网站延迟的开销 ...
- [更新]单线程的JS引擎与 Event Loop
先来思考一个问题,JS 是单线程的还是多线程的?如果是单线程,为什么JavaScript能让AJAX异步发送和回调请求,还有setTimeout也看起来像是多线程的?还有non-blocking IO ...
- Event Loop
Event Loop 是 JavaScript 异步编程的核心思想,也是前端进阶必须跨越的一关.同时,它又是面试的必考点,特别是在 Promise 出现之后,各种各样的面试题层出不穷,花样百出.这篇文 ...
- [转载]JavaScript 运行机制详解:再谈Event Loop
https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5e ...
- setTimeout 的黑魔法 【event loop】
setTimeout,前端工程师必定会打交道的一个函数.它看上去非常的简单,朴实.有着一个很不平凡的名字--定时器.让年少的我天真的以为自己可以操纵未来.却不知朴实之中隐含着惊天大密.我还记得我第一次 ...
- 【朴灵评注】JavaScript 运行机制详解:再谈Event Loop
PS: 我先旁观下大师们的讨论,得多看书了~ 别人说的:“看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲一下就 ...
随机推荐
- hdu4513吉哥系列故事——完美队形II 马拉车
题目传送门 题意:求最长回文串长度,要求回文串左边是非下降. 思路一: 先把连续的回文串,满足先上升再下降的序列处理出来,再对这部分序列做马拉车模板就可以了. 需要注意的是,由于他要的是非下降的序列, ...
- indexOf获取字符位置
先定义一个字符串: var aString = "you are beautiful,so beautiful,and i love you ver much"; 拿到第一个逗号的 ...
- 离散化test
#include<bits/stdc++.h> using namespace std; const int maxn = 1e6+11; int ll[maxn],rr[maxn],ma ...
- Collectors.groupingBy分组后的排序问题
默认groupingBy代码里会生成一个HashMap(hashMap是无序的,put的顺序与get的顺序不一致) HashMap是无序的,HashMap在put的时候是根据key的hashcode进 ...
- Linux中断分层--软中断和tasklet
1. Linux中断分层 (1)上半部:当中断发生时,它进行相应的硬件读写,并“登记”该中断.通常由中断处理程序充当上半部.(一般情况下,上半部不可被打断) (2)下半部:在系统空闲的时候,对上半部“ ...
- ReactJS 页面跳转保存当前scrollTop回来时,自动移动到上次浏览器的位置
在移动端的操作的时候,相信大家都遇到到这种情况,翻了好几页了,点击一项进去查,然后回来的时候,还想回来我原来的位置. google上也找了一此,有一个组件,但是好像是如果想实现这个功能,页面就得用那个 ...
- 成功配置TOMCAT的LOG4J日志系统,格式:HTML+每天以YYYY-MM-DD.LOG命名的日志文件
关于log4j.properties文件在web项目中放的位置,找过很多,最后实践结果是: 一.web项目 二.放在src的目录里面,然后项目生成后会自动在\WEB-INF\classes文件里有份l ...
- java中的各种修饰符作用范围
访问修饰符: private 缺省 protected public 作用范围: 访问修饰符\作用范围 所在类 同一包内其他类 其他包内子类 其他包内非子类 private 可以访问 不可以 不可以 ...
- 内行看门道:看似“佛系”的《QQ炫舞手游》,背后的音频技术一点都不简单
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯游戏云发表于云+社区专栏 3月14日,腾讯旗下知名手游<QQ炫舞>正式上线各大应用商店,并迅速登上App Store免 ...
- Spring MVC处理异常有3种方法
1.使用 SimpleMappingExceptionResolver 实现异常处理 <bean class="org.springframework.web.servlet.hand ...