Appearance
任务轮询/任务管理 Event Loop
js 是一个单线程语言,也就是同一时间只能执行一个任务。为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程阻塞,浏览器会维护一个任务队列,当执行栈中的任务完成后,主线程会读取任务队列,取出排在第一位的一个任务,执行完毕后,再读取任务队列,取出排在第二位的一个任务,以此类推。
- 主线程内的任务执行完成后,才会执行任务队列中的其他任务
- 有新任务到来,主线程会先执行任务队列中的任务,然后再执行主线程任务
任务包括:script、setTimeout、setInterval、UI 交互事件、UI 渲染、Promise、XMLHttpRequest 等
宏任务和微任务
执行过程
同步代码
先执行同步代码
异步代码
放置于任务队列中,等待同步代码执行完成后执行
- 宏任务
- setTimeout
- setInterval
- DOM 渲染
- 微任务
- Promise
先执行微任务队列再执行宏任务队列
- 宏任务
脚本加载
引擎执行任务时不会进行 DOM 渲染,所以如果把 script 标签定义在前面,要执行完成后再渲染 DOM,所以应该将 script 标签定义在 body 标签结束前
定时器
定时器会经过定时器模块处理后放入到异步任务队列,需要等待同步任务执行完成后才执行
微任务
微任务一般有用户代码产生,微任务优先级要高于宏任务。Promise.then 是典型的微任务,实例化 Promise 时执行的代码是同步的,.then
注册的回调函数是异步微任务。
任务执行顺序是同步任务、微任务、宏任务。
示例:下面的这些会如何执行呢?
js
setTimeout(() => {
console.log('setTimeout')
})
new Promise((resolve, reject) => {
console.log('hello promise') // 同步代码
resolve()
}).then(() => {
console.log('then')
})
console.log('hello world') // 同步代码
- 执行到 setTimeout 宏任务,将其放置到宏任务队列,等待执行
- 执行同步代码,输出
hello promise
- 执行到
Promise.then(() => { console.log('then') })
微任务,将其放置到微任务队列,等待执行 - 执行到主代码输出
hello world
- 主线任务执行完毕,通过事件循环遍历微任务队列,将
Promise.then(() => { console.log('then') })
微任务放入主线程执行,输出then
- 主线程执行完毕,通过事件循环遍历宏任务队列,将 setTimeout 宏任务放入主线程执行,输出
setTimeout
js
setTimeout(() => {
console.log('setTimeout')
new Promise((resolve) => {
console.log('setTimeout promise')
resolve()
}).then(() => {
console.log('setTimeout promise then')
})
})
new Promise((resolve, reject) => {
console.log('hello promise')
resolve()
}).then(() => {
console.log('then')
})
console.log('hello world')
- 执行到 setTimeout 宏任务,将其放置到宏任务队列,等待执行
- 执行到主线程代码输出
hello promise
- 执行到
Promise.then(() => { console.log('then') })
微任务,将其放置到微任务队列,等待执行 - 执行到主代码输出
hello world
- 主线程任务执行完成,通过事件循环遍历微任务队列,将
Promise.then(() => { console.log('then') })
微任务放入主线程执行,输出then
- 主线程执行完毕,通过事件循环遍历宏任务队列,将 setTimeout 宏任务放入主线程执行,输出
setTimeout
setTimeout promise
,执行时发现Promise.then(() => { console.log('setTimeout promise then') })
微任务,将其放于微任务队列中 - 主线程任务执行完成,通过事件循环遍历微任务队列,将
Promise.then(() => { console.log('setTimeout promise then') })
微任务放置于主线程中执行,输出setTimeout promise then
- 任务执行完毕
js
let i = 0
setTimeout(() => {
console.log(++i)
})
setTimeout(() => {
console.log(++i)
})
console.log(i)
// 输出结果
// 0
// 1
// 2
- 执行到 setTimeout 宏任务,将其放置到宏任务队列,等待执行
- 执行到主线程代码输出
0
- 执行到
setTimeout(() => { console.log(++i) })
宏任务,将其放置到宏任务队列,等待执行 - 执行到 `setTimeout(() => { console.log(++i) })
## 问题:为什么 setTimeout 和 setInterval 的回调函数执行时间会不准确?
定时器会放置于定时器模块中,在计时结束后,会将回调函数放置于宏任务队列中,等待同步代码执行完成后执行。如果同步代码耗时比较长,就会导致回调函数执行时间不准确。