写这篇文章的原因
这段时间,和前端小伙伴们聊天,一直在谈论Event-loop这个问题。不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容和顺序。
为什么要有Event-loop
因为Javascript
设计之初就是一门单线程语言,因此为了实现主线程的不阻塞,解决用户界面无响应,或者及时处理其他任务。Event Loop这样的方案应运而生,它解决这个问题,使得JavaScript
能够高效处理异步任务
Event-loop解决了什么问题
- 处理耗时操作:在 Web 开发中,包括网络请求、文件读写、数据库访问等操作都是耗时的,如果按照同步方式执行,会导致界面卡顿,用户体验不佳。通过将这些操作转换为异步任务,并在异步任务完成后通知主线程执行回调,可以避免阻塞主线程,提高程序的响应性。
- 支持并发处理:事件循环使得可以同时处理多个任务。通过将任务交给相应的 API 处理后,主线程可以继续执行其他任务,而不必等待异步任务的完成。这样可以极大地提高程序处理并发任务的能力。
- 实现非阻塞 I/O:事件循环使得 JavaScript 能够高效地处理 I/O 操作,特别是在 Node.js 服务器端开发中。通过将 I/O 操作交给操作系统或底层库处理,并在 I/O 操作完成后通知主线程执行回调,可以最大限度地利用计算机资源,提高系统吞吐量。
- 优化性能:事件循环机制能够根据任务的优先级和顺序,合理地安排任务的执行顺序,从而提供更好的性能。例如,事件循环中微任务队列可以优先执行,确保及时响应用户操作,提高程序的流畅度。
宏任务
常见的异步操作:
-
定时器任务(
setTimeout
、setInterval
):通过指定一定的时间间隔或延迟来执行回调函数。 -
UI 渲染:浏览器在重绘和渲染页面时会将相关操作作为宏任务处理。
-
事件监听器(
click
、keyup
等事件):当用户执行某个交互操作时,相应的事件监听器会被触发,将其处理作为宏任务执行。 -
AJAX 请求和服务器响应:当发送 AJAX 请求并接收到服务器响应时,相关的回调函数将被执行作为宏任务。
-
Script标签:保证代码的独立执行,防止页面渲染被阻塞
微任务
-
Promise 的
.then()
和.catch()
:当 Promise 对象的状态改变时,相关的回调函数将被放入微任务队列中,等待执行。 -
MutationObserver:用于监视 DOM 树的变化,并在发生变化时触发相应的回调函数
-
await(下面有扩展)
宏/微任务的执行机制
在事件循环中,每次执行一个宏任务后,会检查微任务队列是否为空。如果有微任务,则会依次执行所有的微任务,直到微任务队列为空。然后,再执行下一个宏任务。这样的机制保证了微任务的执行优先级高于宏任务。
关系图如下:
下面是一个较复杂的代码示例,用来解释宏任务和微任务的执行机制:
js
console.log('Start');
setTimeout(function() {
console.log('Timeout 1');
Promise.resolve().then(function() {
console.log('Promise 1');
});
}, 0);
setTimeout(function() {
console.log('Timeout 2');
Promise.resolve().then(function() {
console.log('Promise 2');
});
}, 0);
Promise.resolve().then(function() {
console.log('Promise 3');
});
console.log('End');
// 执行上述代码的结果如下所示
// Start
// End
// Promise 3
// Timeout 1
// Promise 1
// Timeout 2
// Promise 2
解释运行过程如下:
- 首先输出
Start
。 - 执行第一个
setTimeout
,将定时器任务1添加到宏任务队列中。 - 执行第二个
setTimeout
,将定时器任务2添加到宏任务队列中。 - 执行第一个
Promise.resolve().then()
,将微任务1添加到微任务队列中。 - 输出
End
。 - 当前宏任务执行完毕,检查是否有微任务需要执行。
- 执行微任务队列中的第一个任务,输出
Promise 3
。 - 检查是否有新的宏任务需要执行,发现定时器任务1。
- 等待时间间隔(这里是0毫秒)过去后,定时器任务1被添加到宏任务队列。
- 执行宏任务队列中的第一个任务,输出
Timeout 1
。 - 执行定时器任务1内部的
Promise.resolve().then()
,将微任务2添加到微任务队列中。 - 执行微任务队列中的第一个任务,输出
Promise 1
。 - 检查是否有新的宏任务需要执行,发现定时器任务2。
- 等待时间间隔(这里是0毫秒)过去后,定时器任务2被添加到宏任务队列。
- 执行宏任务队列中的第一个任务,输出
Timeout 2
。 - 执行定时器任务2内部的
Promise.resolve().then()
,将微任务3添加到微任务队列中。 - 执行微任务队列中的第一个任务,输出
Promise 2
。
对于Vue3和react任务队列的理解
任务队列里面同时存在微任务和宏任务,是先执行微任务的,而且要把所有的微任务清空再去执行宏任务,执行宏任务之后就会去进行浏览器渲染,所以微任务在涉及到渲染任务的时候,本质还是一个同步任务,所以 React 的异步更新是一个宏任务。
比如说 Vue3 的更新是微任务,同时有很多个组件需要更新,执行了一个组件的更新任务之后,浏览器是还不会有结果,因为它是微任务,它要等所有组件的更新任务都执行完了才会去进行浏览器渲染,但 React 执行完一个更新任务之后,浏览器就会有结果了,因为 React 更新是宏任务。
微任务说实话就像打补丁一样,不应该执行过多的逻辑,Vue使用微任务因为它不像React是全量更新,更小的颗粒度意味着更小的更新任务,使用微任务足够了。
例如:你有一个 DIV1 和 DIV2,你在 JS 里面进行更新 DIV1,然后再更新 DIV2,在你更新完 DIV1的时候,浏览器肯定是还没更新 DIV1的,因为你这个时候还要执行 DIV2 的更新代码,因为 JS 的执行线程和浏览器的渲染线程是互斥的,所以你要想更新完 DIV1的时候,浏览器就要把它渲染出来,你就要在更新完 DIV1 之后,把控制权交给浏览器的渲染进程,所以你就要用宏任务去更新 DIV1,因为宏任务执行完了之后,控制权将到浏览器的渲染进程上。
扩展
Promise
理解
Promise 是 JavaScript 中用于处理异步操作的内置对象。它被广泛用于解决回调地狱(callback hell)和链式异步操作的问题,提供了一种更优雅和可读性更高的方式来处理异步代码。
Promise 对象实例状态
-
pending(进行中):初始状态,表示异步操作正在进行中,尚未成功或失败。
-
fulfilled(已成功):表示异步操作已成功完成。
-
rejected(已失败):表示异步操作已失败。
Promise 对象方法
-
then(onFulfilled, onRejected):当Promise状态变为fulfilled(解决)时,调用onFulfilled函数;当状态变为rejected(拒绝)时,调用onRejected函数。这个方法可以链式调用,返回一个新的Promise对象。
-
catch(onRejected):捕获Promise链中的任何拒绝错误,并执行相应的回调函数。它也可以被视为then(null, onRejected)的简写形式。
-
finally(onFinally):不管Promise最后是解决还是拒绝,都会执行onFinally回调函数。它返回一个新的Promise对象,允许你在Promise链中进行额外的操作。
-
Promise.resolve(value):返回一个已解决的Promise对象,其值为给定的value。如果value本身就是一个Promise对象,则直接返回这个Promise。
-
Promise.reject(reason):返回一个已拒绝的Promise对象,其原因为给定的reason。
-
Promise.all(iterable):接收一个可迭代对象(如数组),并返回一个新的Promise对象。该Promise对象在可迭代对象中所有的Promise都解决之后才会解决,并将解决结果按照顺序组成一个数组。
-
Promise.race(iterable):接收一个可迭代对象,并返回一个新的Promise对象。该Promise对象将在可迭代对象中的第一个解决或拒绝的Promise发生后立即解决或拒绝,将第一个解决或拒绝的结果作为自己的解决或拒绝结果
async/await
理解
async/await 是建立在 Promise 的基础上的,async 关键字用于修饰一个函数,使其返回一个 Promise 对象,而 await 关键字则用于等待一个 Promise 对象的解决(resolve)或拒绝(reject),并在异步操作完成后获取其结果
补充
实际上最新的规范里已经删掉宏任务的概念了,W3C的解释里是把任务分类,同类型的任务放在相同的队列里,浏览器根据队列的优先级进行获取执行,例如有定时队列、微队列等等,微队列的优先级最高。
至此,文章就分享完毕了。
文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊