任务队列
js 是单线程的, 因为 js 可以操作 DOM, 如果多线程的话, 会造成冲突的问题.
js 的任务分为同步任务和异步任务. 同步任务是指在主线程上依次执行的任务, 形成一个执行栈. 而异步任务不在主线程, 在任务队列中, 如网络请求, 定时器等. 在执行栈的任务执行完毕之后, 系统会检查任务队列, 看是否有可以执行的异步任务.-
而任务队列分为两种, 一种是 mircotask, 另一种是 marcotask. 按照我的理解, mircotask 和 marcotask 的区别在于 mircotask 的任务可以在本次循环 / 页面刷新前被加入到任务队列, 而 marcotask 不可以
- mircotask
- promise
- mutation.oberver
- process.nextTick
- marcotask
- setTimeout,setInterval
- requestAnimationFrame
解析 html
执行主线程 js 代码
修改 url
页面加载
用户交互
浏览器篇
浏览器的 event loops 由 HTML 标准而不是 ECMAScript 定义, 具体可以查看 https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model , 在这里列出比较关键的步奏
检查 macrotask 队列, 运行最前面的任务, 如果队列为空, 前往第二步
检查 mircotask 队列, 一直运行队列中的任务直到该队列为空
渲染过程
执行 resize,scroll, 媒体查询, 动画, 全屏等步奏
运行 animation frame 回调
运行 IntersectionObserver 回调
渲染
回到第一步
因此, eventloop 分为三个阶段, 执行一个 marcotask, 清空 mircotask 队列, 运行 render 阶段
用代码验证一下
- setTimeout(() => {
- console.log('t0')
- Promise.resolve().then(res => {
- console.log('p0')
- })
- })
- let i = 0
- function raf () {
- console.log(i)
- document.querySelector('div').style.width = i * 20 + 'px'
- Promise.resolve().then(res => console.log('p' + i))
- setTimeout(() => {
- console.log('t' + i)
- if (i === 1) {
- document.querySelector('div').style.background = 'red'
- document.querySelector('div').style.height = '50px'
- }
- if (i === 2) {
- let j = 0
- while (j++ <1000000000) {
- }
- document.querySelector('div').style.background = 'blue'
- document.querySelector('div').style.height = '300px'
- }
- if (i === 3) {
- document.querySelector('div').style.width = '40px'
- }
- Promise.resolve(3).then(res => {
- console.log('tp' + i)
- })
- })
- if (++i <= 10) {
- requestAnimationFrame(raf)
- }
- }
- requestAnimationFrame(raf)
输出结果为
t0,p0,0,p1,t1,tp1,1,p2,t2,tp2,2,p3,p4,t4,tp4,t4,tp4,4...
使用 chrome dev tool 的 performance 查看过程
在 Event log 选项卡, 分析一下过程, 可以观察到, 代码执行顺序为, timer,animation frame,paint, 而 timer 和 animation frame 中又会执行属于各自顺序的 mircotasks. 尽管在 i=2 的时候会阻塞代码, 然而还是会执行 ainmtion frame 的代码.
nodejs 篇
nodejs 六阶段
看这篇文章就够了, The Node.js Event Loop, Timers, and process.nextTick() https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
nodejs 的事件循环有六个阶段
timers: setTimeout,setInterval
pending callbacks: 上一轮残留的 IO 回调
idle,prepare: 内部使用
poll: 接受新的 IO 事件, 处理其他阶段不处理的回调, node 在合适的情况会停留在该阶段
check: setImmediate 的回调
close callbacks: 关闭的回调
每个阶段有自己的 callback 队列, 清空了队列或被执行的 callback 达到最大限制, 进入下一个阶段, 此时会运行 process.nextTick
- setImmediate(() => console.log(2));
- setTimeout(() => console.log(1));
- Promise.resolve().then(() => console.log(4));
- process.nextTick(() => console.log(3));
- (() => console.log(5))();
输出 5,3,4,1,2
nodejs 定时器
nodejs 有四种定时器, setTimeout 和 setInterval 算是一种类型, 另外还有 setImmediate 和 process.nextTick 两种类型. 他们跟 mircotask 如 promise 之间的执行顺序是怎样的呢?
有点摸不到头绪? 写个代码看下结果
- setTimeout(() => {
- console.log('t1')
- setTimeout(()=> {console.log('t3')})
- setTimeout(() => console.log('t4'))
- Promise.resolve(1).then(res => console.log('p3'))
- setImmediate(() => {
- console.log('i2')
- setTimeout(() => console.log('t6'))
- process.nextTick(() => {
- console.log('n4')
- process.nextTick(() => {
- console.log('n5')
- })
- })
- Promise.resolve(1).then(res => console.log('p4')).then(res => console.log('p5'))
- })
- setImmediate(() => {
- console.log('i3')
- setImmediate(() => console.log('i4'))
- })
- process.nextTick(() => console.log('n2'))
- process.nextTick(() => console.log('n3'))
- })
- setTimeout(res => {
- process.nextTick(() => console.log('n4'))
- console.log('t2')
- })
- Promise.resolve(1).then(res => console.log('p1')).then(res => console.log('p2'))
- process.nextTick(() => console.log('n1'))
- setImmediate(() => {
- console.log('i1')
- setTimeout(() => console.log('t5'))
- })
输出结果是
n1,p1,p2,t1,t2,n2,n3,n4,p3,i1,i2,i3,n4,n5,p4,p5,t3,t4,t5,t6,i4
过程如下
t1,t2 进 timer 队列, p1 进入 mircotask 队列, i1 进入 setImmediate 队列
运行 process.nextTick, 输出 n1, 清空 mircotask 队列, 输出 p1, 又加入新的 promise 任务, 输出 p2
进入 timers 阶段, 清空 timer 队列
运行 t1, 输出 t1, 然后 t3,t4 加入下一轮的 timers 队列, p3 加入 mircotask 队列, i2,i3 加入 setImmediate 队列, n2,n3 加入 process.nextTick 队列,
运行 t2, 输出 t2,n4 加入 process.nextTick 队列
切换阶段, 清空 nextTick 队列, 输出 n2,n3,n4, 清空 mircotask 队列, 输出 p3
进入 check 阶段
运行 i1, 输出 i1,t5 加入 timers 队列
运行 i2, 输出 i2,t6 加入 timers 队列, 输出 i2,i3,n4 加入 nextTick,p4 加入 mircok 队列
运行 i3, 输出 i3,i4 加入 setImmediate 队列
切换阶段, 运行 n4, 输出 n4, 添加 n5 进队列, 运行 n5, 输出 n5, 清空 mircotask, 运行 p4,p5 加入队列, 输出 p4,p5
timers 阶段 运行 t3,t4,t5,t6
check 阶段, 输出 i4
可以发现, promise 和 nexttick 的任务是添加在本次循环, 其他的是添加到下次循环. 并且, 是按照 timers->nexttick->mircotask->check->nexttick->mircotask->timers 这样的流程来运行
来源: https://juejin.im/entry/5b0a124951882538bd55307c