linux事件循环机制eventloop,理解EventLoop(事件循环)

EventLoop是什么?

EventLoop是一个执行模型,在不同的地方有不同的实现。浏览器和NodeJs基于不同的技术实现了各自的EventLoop。

JS是单线程语言,JS的Event Loop是JS的执行机制。深入了解JS的执行,就等于深入了解JS里的event loop。

浏览器的Event Loop是在html5的规范中明确定义。

NodeJS的Event Loop是基于libuv实现的。可以参考Node的官方文档以及libuv的官方文档。

libuv已经对Event Loop做出了实现,而HTML5规范中只是定义了浏览器中Event Loop的模型,具体的实现留给了浏览器厂商。

JS中的EventLoop

(1)JS为什么是单线程的?

因为JS最初是被设计用在浏览器中,如果JS是多线程的,比如说:有两个线程process1和process2,由于是多线程的JS,两个线程同时对一个dom进行操作,process1删除了该dom,而process2修改了该dom,此时浏览器就没法执行了。所以说JS是单线程的。

(2)JS中为什么需要异步呢?

因为JS中代码自上而下执行,如果不存在异步,当一行代码执行的时间过长时,后边的代码就会被阻塞,对于用户而言,意味着页面卡死,体验极其不好。

(3)JS中如何实现异步的呢?

通过事件循环(EventLoop),理解了EventLoop机制,就理解了JS的执行机制。

(4)JS中的EventLoop

举个栗子1:观察下面代码的执行顺序

console.log(1)

setTimeout(funciton(){

console.log(2)

},0)

console.log(3) // 1 3 2

很显然,setTimeout里的函数并没有立即执行,而是延迟了一段时间,满足一定条件后才执行的,这类代码就叫做异步代码。在JS中就是将任务分为同步任务和异步任务。

按照这种分类方式,JS的执行机制就是:

首先判断JS是同步还是异步代码,同步就进入主线程,异步就进入event table

异步任务在event table中注册函数,当满足触发条件后,被推入event queue

同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主线程中

以上三步循环就是JS中的EventLoop。

举个栗子2:观察下面代码

setTimeout(function(){

console.log("定时器开始啦")

})

new Promise(function(resolve){

console.log("马上执行for循环啦")

for(var i = 0;i < 10000;i++){

i==99 && resolve()

}

}).then(function(){

console.log("执行then函数啦")

})

console.log("代码执行结束")

如果按照上面例1的结论来分析,就是:

setTimeout 是异步任务,被放到event table

new Promise 是同步任务,被放到主线程里,直接执行打印 console.log('马上执行for循环啦')

.then里的函数是 异步任务,被放到event table

console.log('代码执行结束')是同步代码,被放到主线程里,直接执行

所以结果是 【马上执行for循环啦 — 代码执行结束 — 定时器开始啦 — 执行then函数啦】吗?

然而执行后的结果居然不是这样,而是【马上执行for循环啦 — 代码执行结束 — 执行then函数啦 — 定时器开始啦】

那么,难道是异步任务的执行顺序,不是前后顺序,而是另有规定? 事实上,按照异步和同步的划分方式,并不准确。

更准确的划分方式是:

macro-task(宏任务):script,setTimeout,setInterval,setimmediate,requestAnimationFrame (浏览器独有),I/O,UI rendering(浏览器独有)

micro-task(微任务):promise,promise.nextTick,MutationObserver,Object.observe,其中promise.nextTick为node独有

那么按照这种分类方式,JS的执行机制就是

先执行一个宏任务,过程中如果遇到微任务就将其放入微任务的【事件队列】里

当前宏任务执行完毕后,会查看微任务的【事件队列】,并将队列里的微任务依次执行完

重复前面两步

再来分析下这个例子的执行顺序:

首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里

遇到 new Promise直接执行,打印"马上执行for循环啦"

遇到then方法,是微任务,将其放到微任务的【队列里】

打印 "代码执行结束"

本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"执行then函数啦"

到此,本轮的event loop 全部完成。

下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个 setTimeout里的函数,执行打印"定时器开始啦"

关于setTimeout

对于这段代码:

setTimeout(function(){

console.log('开始执行')

},3000)

我们通常的理解是三秒后会打印“开始执行”。其实更准确的说法是:3秒后setTimeout里的函数会被推入event queue中,而event queue(事件队列)里的任务,只有在主线程空闲时才会执行。所以只有当满足3秒后并且主线程空闲时,才会在3秒后执行setTimeout里的函数。如果主线程的内容很多,执行事件超过了3秒,比如10秒,那么setTimeout里的函数只能在10秒后执行了。

浏览器的EventLoop

1. 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等)

2. 全局Script代码执行完毕后,调用栈Stack会清空

3. 从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1

4. 继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;

5. microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空

6. 取出宏队列macrotask queue中位于队首的任务,放入Stack中执行

7. 执行完毕后,调用栈Stack为空

8. 重复第3-7个步骤

9. 重复第3-7个步骤

10. ……

需要注意的是:

1. 宏队列macrotask一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务

2. 微任务队列中所有的任务都会被依次取出来执行,直到microtask queue为空

3. UI rendering的节点,是由浏览器自行判断决定的,但是只要执行UI rendering,它的节点是在执行完所有的microtask之后,下一个macrotask之前,紧跟着执行UI render。

举个栗子:

console.log(1)

setTimeout(() => {

console.log(2)

Promise.resolve().then(() => {

console.log(3)

})

})

new Promise((resolve, reject) => {

console.log(4)

resolve(5)

}).then((data) => {

console.log(data)

})

setTimeout(() => {

console.log(6)

})

console.log(7) //执行结果: 1 4 7 5 2 3 6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值