JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.then,MutationObserver,宏任务的话就是setImmediate setTimeout setInterval
JS 运行的环境。一般为浏览器或者 Node。 在浏览器环境中,有 JS 引擎线程和渲染线程(CSS动画在渲染线程),且两个线程互斥。 Node 环境中,只有 JS 线程。 不同环境执行机制有差异,不同任务进入不同 Event Queue 队列。 当主程结束,先执行准备好微任务,然后再执行准备好的宏任务,一个轮询结束。
调用栈: 当一个 JavaScript 脚本开始执行时,它会被放入调用栈。调用栈是一个数据结构,用于跟踪执行上下文(函数调用)的堆栈。
任务队列: 任务队列是一个先进先出(FIFO)的数据结构,用于存储异步任务。有多个任务队列,其中包括宏任务队列和微任务队列。
JavaScript 的任务分为三类:
- 同步任务(Synchronous Tasks):这些任务会立即执行,直接进入调用栈,直到执行完毕才会从调用栈中移除。因为它们是在当前调用栈中执行的,所以不需要通过事件循环来调度。一般的代码(如变量声明、函数调用等)都是同步任务。
- 宏任务(Macro Tasks):宏任务是指一些大型的任务,它们通常包含了可能会阻塞主线程的大量操作。这些任务会被调度到事件循环的宏任务队列中执行。常见的宏任务有
setTimeout、setInterval、I/O 操作、DOM 操作、AJAX 请求等。每次事件循环都会从宏任务队列中取出一个任务放入调用栈中执行。 - 微任务(Micro Tasks):细粒度的小任务,它们通常是轻量级的、短时间的操作,这些任务会被调度到微任务队列中执行。常见的微任务有
Promise.then()、MutationObserver等。在每次宏任务执行完毕后,事件循环会优先执行微任务队列中的所有任务,然后才会继续执行下一个宏任务。
事件循环的执行顺序
事件循环的运行机制是,先会执行栈中的内容,栈中的内容执行后执行微任务,微任务清空后再执行宏任务,先取出一个宏任务,再去执行微任务,然后在取宏任务清微任务这样不停的循环。
-
eventLoop 是由 JS 的宿主环境(浏览器)来实现的;
-
事件循环可以简单的描述为以下四个步骤:
- 函数入栈,当 Stack 中执行到异步任务的时候,就将他丢给 WebAPIs,接着执行同步任务,直到 Stack 为空;
- 此期间 WebAPIs 完成这个事件,把回调函数放入队列中等待执行(微任务放到微任务队列,宏任务放到宏任务队列)
- 执行栈为空时,Event Loop 把微任务队列执行清空;
- 微任务队列清空后,进入宏任务队列,取队列的第一项任务放入 Stack(栈)中执行,执行完成后,查看微任务队列是否有任务,有的话,清空微任务队列。重复 4,继续从宏任务中取任务执行,执行完成之后,继续清空微任务,如此反复循环,直至清空所有的任务。
输出顺序:
Script start
Script end
setTimeout 1
Promise in setTimeout
setTimeout 2
Script start
Script end
Promise 1
Promise 2
setTimeout
setTimeout222
为什么先执行 setTimeout (0 毫秒延迟): 延迟时间的作用setTimeout 的延迟时间决定了宏任务的计划执行时间。一个宏任务的延迟时间较短(例如 0 毫秒)意味着它会更早被执行。
const first = () =>
new Promise((resolve, reject) => {
console.log(3)
let p = new Promise((resolve, reject) => {
console.log(7)
setTimeout(() => {
console.log(5) resolve(6)
}, 0)
resolve(1) })
resolve(2)
p.then((arg) => {
console.log(arg)
})
})
first().then((arg) => {
console.log(arg)
})
console.log(4)
输出结果:
同步:3-7-4
宏任务:5
微任务:处理p的结果1-处理first的结果2
结果:3-7-4-1-2-5
理解为什么p的结果是1而不是6,应为resolve(1)是同步任务,先执行;setTimeout中的resolve(6)后执行,将会被忽略,如果没有外部的resolve(1),那么p的结果就是6了。