循环太慢_前端中的 Event Loop 事件循环机制

0a4ebfa11fd109dfa4dc8e2ed8196c76.png

什么是事件循环 - Event Loop

Event Loop 指的是计算机系统的一种运行方式

为什么 JS 需要 Event Loop

在回答这个问题之前,我们需要简单了解一个知识点 - JS 是单线程的

为什么 JS 是单线程的

JS 语言的一大特点就是单线程的,所谓的单线程就是,同一时间只能做一件事情

就像银行一个柜台,一次只能服务一个客户,糟糕,这效率太慢了!

为什么 JS 不能有多个线程呢?JS 的主要用途是操作用户的交互,以及操作 DOM。这决定了它只能是单线程的,否则会带来很多麻烦。假设 JS 同时有两个线程,一个线程在某个 DOM 节点上添加内容,而另一个线程删除了这个节点,这时浏览器应该以哪个进程为准?

回到银行的比喻,一个柜台同时来了两个客户,第一个客户需要对 A 账号存入 100 元,而另一个客户需要冻结 A 账号,这时柜台小姐姐应该听谁呢?

在 《你不知道的 JavaScript》书中提到,多线程编程是非常复杂的,可能会得到出乎意料的、不确定的行为,通常这很让人头疼。

既然我们已经了解了 JS 是单线程的,那为什么需要 Event Loop 呢?

Javascript是单线程的,单线程就意味着所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

如果其中一个任务很慢,占用了很多的时间,此时网页就可能卡住

有些 I/O (输入输出) 操作是很慢的,比如 Ajax 操作从网络读取数据

JS 语言的设计者意识到,主线程可以不管这些 I / O 操作,把等待中的任务挂起,先运行排在后面的任务。等待 I / O 操作返回结果,再去执行挂起的任务

因此任务可以分为两种,一种是同步任务,一种是异步任务

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

异步任务指的是,不进入主线程,而是进入任务队列,通过 Event Loop 机制等待合适的时间调用

有了 Event Loop 的加持,JS 才能非阻塞地运行


宏任务 和 微任务

宏任务

宏任务 macotask, 也叫 tasks。一些异步任务的回调会依次进入 macro task queue,等待后续被调用,这些异步任务包括:

  • setTimeout
  • setInterval
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有)

微任务

微任务 micotask,也叫 jobs。另一些异步任务的回调会依次进入 micro task queue,等待被调用,这些异步任务包括:

  • Promise
  • Object.observe
  • MutationObserver

浏览器的 Event Loop

我们先来看一张图

d121af647e91d81c8c7f7608cb90615c.png

先别被这张图吓坏,看看执行一个 JavaScript 代码的具体流程

  1. 执行全局 Script 代码,这些代码中有同步语句或异步语句,遇到同步语句直接执行,异步语句放入宏任务或微任务的队列。
  2. 全局 Script 代码执行完毕后,调用栈 Stack 会清空
  3. 从微任务中取出位于队首的回调任务,放入调用栈 Stack 中执行,执行完成后 微任务队列长度减一
  4. 继续取出位于队首的任务,放入调用栈 Stack 中执行,以此类推,直到把 微任务队列 中的所有任务都执行完毕。注意,如果在执行微任务过程中,又产生了新的微任务,那么会加入到微任务队列的尾部,也会在这个周期被执行
  5. 当 微任务队列 中的所有任务都执行完毕后,此时 微任务队列 为空,调用栈 Stack 也会空
  6. 取出宏任务中的队首的任务放入 Stack 中执行
  7. 执行完毕后,调用栈Stack为空
  8. 重复第3-7个步骤
  9. 重复第3-7个步骤
  10. ...

可以看到,这就是浏览器的事件循环 Event Loop

这里归纳3个重点:

  1. 宏任务一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
  2. 微任务队列中所有的任务都会被依次取出来执行,直到 微任务队列 为空;
  3. 图中没有画UI rendering的节点,因为这个是由浏览器自行判断决定的,但是只要执行UI rendering,它的节点是在执行完所有的 微任务 之后,下一个 宏任务 之前,紧跟着执行UI render。

你大概会有这种感觉

把 Event Loop 当做一个游乐场的游戏,宏任务队列 中的小朋友(任务),玩过一次游戏后,需要重新到队尾排队才能再玩,而 微任务队列 中的小朋友(任务),可以插队继续玩


好了,概念性的东西就这么多,来看几个示例代码,测试一下你是否掌握了:

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);

....... 5 秒后公布答案

开玩笑,现在就公布

// 正确答案
1
4
7
5
2
3
6

你答对了吗?答对请扣1,答错请扣 2

我们来看看完整的执行流程吧

Step 1

console.log(1); // 同步代码直接执行

目前打印列表 1

宏任务队列 []

微任务队列 []

Step 2

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

setTimeout 属于宏任务,放入宏任务队列

目前打印列表 [1]

宏任务队列 [setTimeout1]

微任务队列 []

Step 3

new Promise((resolve, reject) => {
  // Promise 新建后就会立即执行,打印出 4
  console.log(4)
  resolve(5)
}).then((data) => {
  // then() 注册的回调,属于微任务,放入微任务队列
  console.log(data);
})

目前打印列表 1 4

宏任务队列 [setTimeout1]

微任务队列 [Promise1]

Step 4

setTimeout(() => {
  console.log(6);
})

同上,setTimeout 放入宏任务队列

目前打印列表 1 4

宏任务队列 [setTimeout1, setTimeout2]

微任务队列 [Promise1]

Step 5

console.log(7) // 同步代码,直接执行

目前打印列表 1 4 7

宏任务队列 [setTimeout1, setTimeout2]

微任务队列 [Promise1]

----------------- 一条优美的分割线 ------------------

至此,全局 Script 代码已经执行完了

还记得微任务可以插队执行吗?

所以先依次从 微任务队列 中取出队首任务执行,直到 微任务队列 为空

------ 目前的任务队列

宏任务队列 [setTimeout1, setTimeout2]

微任务队列 [Promise1]

------

Step 7

console.log(data)       // 这里data是Promise的决议值5

目前打印列表 1 4 7 5

宏任务队列 [setTimeout1, setTimeout2]

微任务队列 []

微任务队列已空,开始执行宏任务队列

Step 8

console.log(2) // 直接打印
Promise.resolve().then(() => {
    // then() 注册的回调,属于微任务,放入微任务队列
    console.log(3)
  });

目前打印列表 1 4 7 5 2

宏任务队列 [setTimeout2]

微任务队列 [Promise2]

Step 9

注意刚刚 微任务队列 增加了一个任务,还记得 微任务 会插队吗,所以现在要先执行 微任务队列的任务,直到 微任务队列 清空

console.log(3) // 直接打印

目前打印列表 1 4 7 5 2 3

宏任务队列 [setTimeout2]

微任务队列 []

微任务队列已空,继续执行宏任务队列

Step 10

console.log(6) // 直接打印

目前打印列表 1 4 7 5 2 3 6

宏任务队列 []

微任务队列 []

全部执行完毕,经过一步一步的分析,Event Loop 机制是不是更好理解呢?


总结:

  1. JavaScript 是单线程的,有了 Event Loop 的加持,JS 才可以非阻塞地执行
  2. 浏览器的 Event Loop 机制,分为 宏任务微任务
  3. 微任务比宏任务优先级高,事件循环开始后,微任务首先执行,直到微任务队列清空,然后执行宏任务队列,如果宏任务队列执行过程中,产生了新的微任务,需要立刻执行微任务,直到微任务队列清空,然继续执行宏任务,直到宏任务队列清空
  4. 宏任务: setTimeout、setInterval、requestAnimation(浏览器)、IO、UI rendering
  5. 微任务: process.nextTick(Node)、Promise、Object.observe、MutationObserver
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值