一图胜千言
有英文基础的小伙伴可以直接看这篇原文
jakearchibald.com/2015/tasks-…
微任务
先来做个题,请说下列代码的打印顺序:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
321...
script start
script end
promise1
promise2
setTimeout
如果已经知道为什么是这个顺序的小伙伴可以划走了,这篇文章属于科普篇,并不会讲出多少新花样来.
ok,接下来就让我们开始引入今天的主角microtask
在JS中分为两种任务类型:macrotask
和microtask
.
macrotask
(宏任务,官方并没有定义过主要是用于区分微任务,官方使用的名词是Task
),包括script整体代码、setTimeout、setInterval、I/O操作等- 每个task都会从头到尾将这个任务执行完毕,不会执行其他
- 浏览器为了使得JS内部task跟DOM任务有序执行,会在每个task结束之后,下个task开始执行前对页面进行重新渲染
microtask
(微任务),很有意思,微任务包括MutationObserver
跟Promise
的回调- 它是在什么时候执行呢,在task结束后,页面重新渲染前
- 它的响应速度比
setTimeout
快(因为setTimeout
是task
) - 也就是说在某
个宏任务
执行完毕后,会将在它执行过程中产生的(在执行宏任务过程中被放入微任务列表的)所有微任务
都给执行了 - 还有个有意思的点,在node环境下,
process.nextTick
的优先级高于Promise
,也就是说在宏任务
结束后会先执行微任务队列中的nextTickQueue
部分然后再执行其他的微任务
,Promise
优先级要高于MutationObserver
.
再来聊一下事件循环,我是这么理解的,每个页面都是一个进程,都有一个task队列,这个队列由浏览器的事件循环机制维护,JS引擎负责执行代码并维护执行栈,浏览器的事件循环负责调度和管理任务队列会从任务队列中取出任务放入执行栈中,而任务是由Web APIs
(如setTimeout,DOM事件,HTTP请求等)推入队列的,这些API在JavaScript执行时被调用,并在合适的时间将任务推送到任务队列.
- 执行栈(调用栈):由JS引擎维护
- 任务队列:分为
宏任务队列
跟微任务队列
,由浏览器事件循环负责 - 任务是由
Web APSs
推入队列的 - 浏览器渲染:会在执行栈执行完一个
宏任务
并把当前的微任务队列
都执行完毕后执行(操作DOM跟渲染DOM分开)
再说一下一个特殊的
task
--requestAnimationFrame
,它不是微任务,同时跟其余宏任务(Tsak)
的执行顺序还不太一样,requestAnimationFrame
的回调不在常规的任务队列中,而是与浏览器的渲染周期同步。
window.requestAnimationFrame()
方法会告诉浏览器你希望执行一个动画。它要求浏览器在下一次重绘之前,调用用户提供的回调函数。
function animate() {
console.log('Animation frame');
// requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
setTimeout(() => {
console.log('Timeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
/** 结果
* Promise
* Animation frame
* Timeout
*/
执行顺序
-
执行当前的宏任务。
-
执行所有微任务。
-
执行 requestAnimationFrame 的回调。
-
进行渲染更新。
ok,关于浏览器的事件循环就聊到这,现在AI如此发达,有啥不懂的直接ChatGPT或其他大模型都能给你讲的明白的,并且如果理解不透彻的还可以刨根问底继续问,真是一个改变学习习惯的时代.