Appearance
宏任务
宿主环境提供的异步方法,都是宏任务。
全局代码(script 脚本)
UI 渲染(部分浏览器实现会把UI渲染当成宏任务)
DOM 事件监听的回调
setTimeout 回调,等待时间到达后才将回调放入宏任务队列。
setInterval 回调
setImmediate(Node.js 支持,浏览器中仅IE和Edge部分实现,标准不支持)
XMLHttpRequest/fetch等回调(onload、onerror等)
messageChannel
javascriptconst channel = new MessageChannel() channel.port1.postMessage('msg') channel.port2.onmessage = e => { console.log(e.data) } Promise.resolve().then(() => { console.log('@') })IndexedDB 的回调
History API 的某些回调
WebSocket 的事件回调(onmessage, onopen, onclose等)
FileReader、File API 的事件回调
requestAnimationFrame(不是宏任务,也不是微任务,单独的队列,发生在渲染前)
Nodejs 中的宏任务
- 全局代码(script)
- setTimeout/setInterval
- setImmediate
- I/O 回调(如 fs、net、http等异步回调)
- process.nextTick(注意这是微任务!)
- MessageChannel(node也支持)
- 各种事件(如 'data', 'end', 'error' 等 EventEmitter 回调)
微任务
由语言标准提供。
Promise 相关方法(new Promise() 执行器中的代码属于同步任务)
p.then()
p.catch()
p.finally()
Promise.resolve.then()
Promise.reject.then()
await 之后的代码
javascriptawait foo() // 这一行是同步代码 bar() // 等价于 new Promise((resolve, reject) => { foo() }).then(() => { bar() })new MutationObserver(cb),监听标签的变化,标签只要有任何变化,对应回调就会在微任务异步执行
queueMicrotask Promise.resolve().then()
jswindow.queueMicrotask = function (callback) { Promise.resolve() .then(callback) // 定时器是为了把异常抛到宏任务队列,这样就变成了全局未捕获异常,就不会阻塞 // 后续 Promise 链中的微任务 .catch(e => setTimeout(() => { throw e })) }process.nextTick(Nodejs)
事件循环机制:
JS 代码执行时,浏览器分配一个 JS 引擎线程(主线程),里面有一个 ECStack,用来自上而下执行代码,以函数调用为单位入栈/出栈,执行时会判断是同步代码还是异步代码,如果是同步代码立即执行,如果是异步代码,JS 引擎“注册”这个异步任务,代码不会立即执行,主线程继续往下执行。异步任务会注册到浏览器的 EventLoop 中,EventLoop 会将异步任务分为宏任务和微任务。分别用宏任务队列和微任务队列存储它们。等到所有的同步代码执行完后,主线程空闲下来了,执行一个宏任务(比如整个 script 脚本或 setTimeout 回调),执行过程中遇到微任务(如 Promise.then),会将其加入微任务队列,当前宏任务执行完成后,立即清空所有微任务队列(依次执行所有微任务),微任务全部执行完毕后,浏览器有机会进行一次页面渲染(GUI 渲染),但渲染可能被浏览器优化跳过(合并多次 DOM 更新优化性能)。之后,从宏任务队列中取出下一个宏任务,循环往复。
同步:任务是依次执行的,上一个任务没有完成,下一个任务不能进行处理(js单线程)
异步:上一个任务没有完成,下一个任务也可以继续处理(浏览器多线程)
页面一直都是绿色,不会经历 red => green 的过程,因为 GUI 渲染线程在微任务清空完后才执行。
js
document.body.style.background = 'red'
Promise.resolve().then(() => {
document.body.style.background = 'green'
})有两个宏任务,全局 script 和 setTimeout,全局代码执行完后,可能执行 GUI 渲染线程,setTimeout 执行完后一定执行 GUI 渲染线程,因为页面可能由 red => green,也可能一直是 green,GUI 渲染线程未必每次都执行,浏览器可能优化。
js
document.body.style.background = 'red'
setTimeout(() => {
document.body.style.background = 'green'
})因为宏任务是一个一个取出的,所以输出顺序为 1 2 3 4
javascript
btn.addEventListener('click', () => {
console.log('1')
Promise.resolve().then(() => {
console.log('2')
})
})
btn.addEventListener('click', () => {
console.log('3')
Promise.resolve().then(() => {
console.log('4')
})
})同步代码,直接在执行栈中将两个回调执行,因此输出顺序是 1 3 2 4
javascript
btn.addEventListener('click', () => {
console.log('1')
Promise.resolve().then(() => {
console.log('2')
})
})
btn.addEventListener('click', () => {
console.log('3')
Promise.resolve().then(() => {
console.log('4')
})
})
btn.click() // 同步代码js
Promise.resolve().then(() => {
console.log('then1')
Promise.resolve().then(() => {
console.log('then1-1')
}).then(() => {
console.log('then1-2')
})
})
.then(() => {
console.log('then2')
})
.then(() => {
console.log('then3')
})
.then(() => {
console.log('then4')
})
.then(() => {
console.log('then5')
})
// then-1
// then1-1
// then2
// then1-2
// then3
// then4
// then5
// 第一轮微任务队列
// 初始:then1 进入微任务队列
// 执行 then1
// 打印:then1
// 注册:Promise.resolve().then(...)(then1-1)
// 注册:then1的下一个 then(then2)作为微任务
// 第二轮微任务队列
// then1-1(嵌套 promise 的 then)
// then2(主链 then)
// 执行 then1-1
// 打印:then1-1
// 注册:then1-2(then1-1 的 then)
// 执行 then2
// 打印:then2
// 注册:then3(then2 的 then)
// 第三轮微任务队列
// then1-2
// then3
// 执行 then1-2
// 打印:then1-2
// 执行 then3
// 打印:then3
// 注册:then4
// 第四轮微任务队列
// then4
// 执行 then4
// 打印:then4
// 注册:then5
// 第五轮微任务队列
// then5
// 执行 then5
// 打印:then5js
// https://juejin.cn/post/6949699310732869669
Promise.resolve().then(() => {
console.log('then1')
Promise.resolve().then(() => {
console.log('then1-1')
return Promise.resolve()
}).then(() => {
console.log('then1-2')
})
})
.then(() => {
console.log('then2')
})
.then(() => {
console.log('then3')
})
.then(() => {
console.log('then4')
})
.then(() => {
console.log('then5')
})
// then-1
// then1-1
// then2
// then3
// then4
// then1-2
// then5
// Promise.resolve().then(() => {
// return Promise.resolve(4);//return了一个Promise
// })
// .then((res) => {
// console.log(res)
// })
// 可以理解为
// Promise.resolve()
// .then(() => {
// return 4;
// })
// .then()
// .then()
// .then((res) => {
// console.log(res)
// })js
function light () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('red')
resolve()
}, 3000)
}).then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('green')
resolve()
}, 2000)
})
}).then(() => {
return light()
})
}为什么要有微任务?只有宏任务可以吗? 宏任务遵循谁能执行了就会先执行的原则执行,只有宏任务的情况下,如果后面有一个优先级很高, 要立即执行的代码,也只能后面执行 所以只有宏任务是不行的,因为可能要处理高优先级的任务,所以引入了微任务的概念
Node中的事件循环和浏览器中的事件循环有什么区别? Node宏任务执行顺序 1.timers定时器:执行已经安排的setTimeout和setInterval的回调 2.pending callback(待执行回调):执行延迟到下一个循环迭代的I/O回调 3.idle,prepare:仅系统内部使用 4.poll:检索新的I/O事件,执行与I/O相关的回调 5.check:执行setImmediate()的回调 6.close callback:WebSocket.on('close', () => {})
微任务和宏任务在Node中的执行顺序(Node 10为界限) Node v10及以前 1.执行完一个阶段中的所有宏任务(上面的1-6) 2.执行nextTick队列的内容 3.执行微任务
Node v10以后: 和浏览器的行为统一
js
// example1
async function async1 () {
console.log('async1 start')
await async2()
console.log('async1 end')
// await async2()可以理解为
// new Promise(() => {
// async2() // 所以是async2()立即执行的,但是await后面的代码都相当于放到了.then中,所以上面代码意思就是
// }).then(() => {
// console.log('async1 end')
// })
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0);
async1()
new Promise((resolve) => {
console.log('Promise1')
resolve()
}).then(() => {
console.log('Promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// Promise1
// script end
// async1 end
// Promise2
// setTimeout
// example2
console.log('start')
setTimeout(() => {
console.log('children2')
Promise.resolve().then(() => {
console.log('children3')
})
}, 0);
new Promise((resolve, reject) => {
console.log('children4')
setTimeout(() => {
console.log('children5')
resolve('children6')
}, 0);
}).then((value) => {
console.log('children7')
setTimeout(() => {
console.log(value)
}, 0);
})
// start
// children4
// children2
// children3
// children5
// children7
// children6
// example3
const p = function () {
return new Promise((resolve, reject) => {
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 0);
resolve(2)
})
p1.then((value) => {
console.log(value)
})
console.log(3)
resolve(4)
})
}
p().then((value) => {
console.log(value)
})
console.log('end')
// 3
// end
// 2
// 4javascript
console.log(1)
async function async () {
console.log(2)
await console.log(3)
console.log(4)
}
setTimeout(() => {
console.log(5)
}, 0);
const promise = new Promise((resolve, reject) => {
console.log(6)
resolve(7)
})
promise.then(res => {
console.log(res)
})
async()
console.log(8)
// 1
// 6
// 2
// 3
// 8
// 7
// 4
// 5