前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >带你了解Event Loop

带你了解Event Loop

原创
作者头像
三点水的洋
发布2022-06-10 18:13:40
6900
发布2022-06-10 18:13:40
举报
文章被收录于专栏:前端开发进阶前端开发进阶

javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的“多线程”都是用单线程模拟出来的。但对于一些异步操作JS是如何使用Event Loop去处理他们不会导致阻塞呢,我们下面来看一下。

  • Event Loop 是什么?
  • Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
  • 比如加载一个多媒体网页,页面骨架屏和页面元素的渲染是同步任务,图片、音乐、视频耗时比较久的是异步任务

下面来看一下js在内存中的运行方式:

如上图所示,同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。 当指定的事情完成时,Event Table会将这个函数移入Event Queue。 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

为了更好的了解这个机制,我们来进行一些小测试来试验下

测试一:

代码语言:javascript
复制
console.log(1)

setTimeout(() => {
	console.log(2)
}, 0)

Promise.resolve().then(() => {
	console.log(3)
}).then(() => {
	console.log(4)
})

console.log(5)

可能大家都知道,上面代码会先输出 1 5,但是到底是先执行setTimeout还是Promise呢,把上面代码复制到浏览器控制台中执行输出 1/5/3/4/2,说明在浏览器中,Promise执行的顺序比setTimeout高,这是为什么呢?

浏览器中的Event Loop

下面我们来看一下在浏览器中Event Loop的机制:

我们可以看到,定时器和一些异步xhr属于 Task Queue这个队列,Promise和mutaition observer 属于一个Microtask Queue这个队列,Event Loop执行一次,先检查microtask队列是否为空,不为空的话依次执行直至清空队列,然后再执行Task Queue。

经过上面的分析,我们直到Promise的优先级为什么会比timer类型的高,下面我们再来看一题

测试二:

代码语言:javascript
复制
console.log(1)

setTimeout(() => {
	console.log(2)
	new Promise(resolve => {
		console.log(4)
		resolve()
	}).then(() => {
		console.log(5)
	})
})

new Promise(resolve => {
	console.log(7)
	resolve()
}).then(() => {
	console.log(8)
})

setTimeout(() => {
	console.log(9)
	new Promise(resolve => {
		console.log(11)
		resolve()
	}).then(() => {
		console.log(12)
	})
})

经过上面的分析,对于这个测试估计已经是信心满满了。

首先输出1,然后执行Promise中的7,8,然后执行第一个timer接着输出2,然后执行里面的Promise执行4,5,然后执行第二个timer输出9 ,11,12,没错,正确答案就是 1,7,8,2,4,5,9,11,12

Node环境中的Event Loop

看完了浏览器中的Event Loop,下面我们来看一下node环境中的,在Node环境中运行以下代码会怎么输出呢?

测试三:

代码语言:javascript
复制
console.log(1)

setTimeout(() => {
	console.log(2)
	new Promise(resolve => {
		console.log(4)
		resolve()
	}).then(() => {
		console.log(5)
	})
	process.nextTick(() => {
		console.log(3)
	})
})

new Promise(resolve => {
	console.log(7)
	resolve()
}).then(() => {
	console.log(8)
})

process.nextTick(() => {
	console.log(6)
})

其它的咱们上面已经分析过,但这次比上面多了一个 peocess,它的优先级是怎么样的呢?

我们先来看下Node环境下的Event Loop(Node中libuv模块)

上图的意思是

1. 先执行即到期的setTimeout/setInterval;

2. 再执行I/O 事件;

3. 执行setimmediates注册的函数;

4. 执行 close handlers,比如tcp连接断开等;

重要的是,在执行以上每一种事件类型之间,会先清空上图中中间的 nexy tick queue 和Micro task Queue队列,会优先优先清空next tick queue,即通过process.nextTick注册的函数

经过上面的分析,相信大家已经把测试三的答案写出来了,没错就是1、7、6、8、2、4、3、5

上面的这个gif图也直观的解释了运行机制,Poll 阶段是I/O阶段,check queue 是setImmediate

当然,这只是比较浅显的理解,具体的大家可以去看libuv的源码。

应用

其实Node的单线程非阻塞 IO 模型,就是基于这种Event Loop来实现的,具备强大的并发能力。

先来说一下为什么Node会采用单线程:因为多线程切换会有CPU消耗,将第一个线程的state写到内存里,再把要执行的线程的state加载到寄存器和缓存里,但是采用了单线程却无法进行任务的切换,I/O会使机器block,所以不能在主进程中进行I/O,要使用异步IO。

上图的例子中

收到请求1,开始处理请求 进行请求1的 IO 读取,并注册一个回调函数(处理数据并响应客户端),同时线程不阻塞,继续处理请求2 进行请求2的 IO 读取,并注册一个回调函数(处理数据并响应客户端),同时线程不阻塞,继续处理剩下的请求 请求处理结束后,依次执行 IO 读取是注册的回调函数(处理数据并响应客户端),完成处理。

Node默认4个I/O线程~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 浏览器中的Event Loop
  • 应用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档