Js线程、EventLoop、事件循环、消息队列

首先我们都知道js是单线程,之前一直很困惑,明明js是单线程,为什么还可以处理异步函数。现在终于知道,虽然js是单线程的,但是浏览器或者node(宿主环境)中会存在其他的线程来辅助js引擎线程的运行。

今天我们来讨论一下当js在浏览器中运行时,js引擎线程的运行过程以及浏览器中的辅助线程如何帮助js引擎线程运行。

1、 浏览器

浏览器中有很多线程去辅助js引擎线程的运行,这些线程包括:

  • 定时器触发线程(setTimeout)

  • http异步线程

  • 浏览器事件线程(onclick)

  • GUI渲染线程

  • js引擎线程

  • 事件轮询线程(Event Loop)

其中浏览器事件线程与事件轮询线程并不是官方的说法。不过这里为了便于理解,就先这样定义。接下来我们主要讨论定时器触发线程(setTimeout)、http异步线程、事件轮询线程(Event Loop)、js引擎线程、浏览器事件线程。

2、线程与进程

首先我们来了解一下线程与进程的概念。

我们打开任务管理器,可以看到正在运行的进程,可以认为,一个进程相当于一个应用程序。一个进程的运行可以有多个线程的相互配合,比方说QQ进程的运行,需要有接受消息线程,文件传输线程等线程的配合。而我们打开一个网页就相当于开启了一个进程,打开3个网页就相当于开启了3个进程,一个网页进程的正常运行,也需要许多线程的配合,一些主要的线程包括:

类型A:GUI渲染线程

类型B:js引擎线程

类型C:事件轮询线程

类型D:定时器触发线程、http异步线程、浏览器事件线程

我们现在只考虑类型B,类型C,类型D之间的关系。

3、js引擎线程

js引擎线程就是执行js代码的线程,也就是主线程。一般来说,如果是同步代码,就可以在主线程中直接运行,如果是异步代码,就交给其他线程运行,异步函数执行成功后的回调函数会放在消息队列中,在适当的时候放入主线程中执行。比如:

1 var a = 2;
2 setTimeout()
3 ajax()
4 console.log()

第1,2行代码会直接在主线程中运行,而第2行代码会交给定时器触发线程(setTimeout)处理,第3行代码会交给http异步处理线程,异步处理线程主要做两件事:

  1. 处理主线程丢过来的代码

  1. 保存好异步函数执行成功后的回调函数

主线程在运行代码时,会形成一个执行栈,处理函数的嵌套问题。

4、定时器触发线程(setTimeout)、http异步线程、浏览器事件线程(onclick)

主线程在遇到异步代码时,会交给对应的异步线程去处理,比如:

1 var a = 2;
2 setTimeout(fun A)
3 ajax(fun B)
4 console.log()
5 dom.onclick(func C)

第1,4行是同步代码,直接在主线程中运行,第2行是异步代码,交给定时器触发线程去执行,第3行是异步代码,交给http处理线程去执行,而第5行也是异步代码,交给浏览器事件线程去执行。

主线程把setTimeout、ajax、dom.onclick分别给三个线程,他们之间有些不同

  • 对于setTimeout代码,定时器触发线程在接收到代码时就开始计时,时间到了将回调函数扔进队列

  • 对于ajax代码,http 异步线程立即发起http请求,请求成功后将回调函数扔进队列

  • 对于dom.onclick,浏览器事件线程会先监听dom,直到dom被点击了,才将回调函数扔进队列

5、消息队列

可以理解为一个静态的队列存储结构,非线程,只做存储,里面存的是一堆异步成功后的回调函数字符串,肯定是先成功的异步的回调函数在队列的前面,后成功的在后面。

注意:是异步成功后,才把其回调函数扔进队列中,而不是一开始就把所有异步的回调函数扔进队列。比如setTimeout 3秒后执行一个函数,那么这个函数是在3秒后才进队列的。

6、事件轮询线程(Event Loop)

事件轮询线程就是主线程、其他的异步线程、消息队列三者之间沟通的桥梁,看一下如下例子:

setTimeout(() => {
    console.log(1)
}, 2000)
setTimeout(() => {
    console.log(2)
}, 3000)

一开始,消息队列是空的,第1个定时器会交给定时器处理线程进行处理,2秒之后会将它的回调函数放入消息队列当中,同理,第2个定时器会交给定时器处理线程进行处理,3秒之后会将它的回调函数放入消息队列当中,如图过程:

顺时针来看,这是大概的步骤,当主线程的任务执行完之后,会不断的询问消息队列中是否有任务,如果有任务,就放到主线程中去执行。

7、实例

var a = 111;


setTimeout(function() {
    console.log(222)
}, 2000)

fetch(url)  // 假设该http请求花了3秒钟
.then(function() {
    console.log(333)
})

dom.onclick = function() {  // 假设用户在4秒钟时点击了dom
    console.log(444)
}


console.log(555)


// 结果
555
222
333
444

步骤1:

主线程只执行了var a = 111;和console.log(555)两行代码,其他的代码分别交给了其他三个线程,因为其他线程需要2、3、4秒钟才成功并回调,所以在2秒之前,主线程一直在空闲,不断的探查队列是否不为空。

此时主线程里其实已经是空的了(因为执行完那两行代码了)

步骤2:

2秒钟之后,setTimeout成功了

步骤3:

步骤4:

注意:

图里的队列里都只有一个回调函数,实际上有很多个回调函数,如果主线程里执行的代码复杂需要很长时间,这时队列里的函数们就排着,等着主线程啥时执行完,再来队列里取

所以从这里能看出来,对于setTimeout,setInterval的定时,不一定完全按照设想的时间的,因为主线程里的代码可能复杂到执行很久,所以会发生你定时3秒后执行,实际上是3.5秒后执行(主线程花费了0.5秒)

8、结尾

这是我在了解了Js线程、EventLoop、事件循环、消息队列相关知识后写下的笔记。原文是掘金上的,链接为:

作者:晴天633

链接:https://juejin.cn/post/6844903752621637645

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值