Event Loop & vue 中的应用

733 阅读1分钟

前言

js的事件循环机制对于我们理解阅读代码的执行顺序是必不可少的知识点。在 vue 中虚拟 DOM 对比之后的重渲染便是参考这种机制

Event Loop

1628068869156.png

js 是单线程语言,干完一件事之后才能干下一件事

js 执行的代码可分为同步和异步,同步代码执行完毕再执行异步任务

异步任务分为宏任务和微任务

  1. 执行同步代码
  2. 执行微任务队列
  3. 执行宏任务队列
  4. 重复 2 => 3 过程,直到没有任务队列

微任务

promise 就是典型的微任务

console.log(1)
const p1 = new Promise((resolve, reject) => {
  console.log(2)
  resolve()
})
p1.then(() => {
  console.log(3)
})
console.log(4)

上面的执行结果是

1 2 4 3

promise 入参的函数属于同步环境,故2在4前面。promise.then .catch .finally 的函数,就是要执行的微任务

微任务有

Promise.then .catch .finally
MutationObserver // 检测 DOM,DOM3
Object.observe // 已经废弃 Object.defineProperty 和 Proxy 都是同步的
process.nextTick // node.js 独有,node.js中优先级高于 promise

宏任务

setIntervalsetTimeout 是两个常见的任务

console.log(1)
setTimeout(() => {
  console.log(2)
})
console.log(3)

上面的执行结果

1 3 2

当 setTimeout 执行完毕的时候,会把 () => { console.log(2) } 作为宏任务放入宏任务队列

再来看个例子,setTimeout 的执行可能和预设的时间不一致

const len = 1 * 1000 * 1000 * 1000

console.time('setT')
console.time('hihi')

setTimeout(() => {
  console.timeEnd('setT') // setT: 492.93115234375 ms
}, 100)

for(let i = 0; i < len; i++) {
}

console.timeEnd('hihi') // hihi: 491.435791015625 ms

宏任务有

整个script
setTimeout 
setInterval
setImmediate // node.js 独有,优先级低于 setTimeout

vue 中的应用

vue 更新 DOM 是异步更新的,更新的内容会形成一个队列,这个队列是去重的

const count = ref(1);
watchEffect(() => {
    console.log(count.value);
});
const add = () => {
    count.value++;
    count.value++;
};

当触发 add 方法之后会调用两次 count 的叠加,但是 watchEffect 只会监听到一次变化,这就是去重的异步更新队列

$nextTick

const count = ref(1);
watchEffect(() => {
    console.log(count.value);
});
const add = () => {
    count.value++;
    count.value++;
    nextTick(() => {
        console.log('nextTick')
    })
};

在上面的基础上添加 nextTick 方法,执行回调的时机在于 DOM 更新之后,也可以理解成 updated 生命周期钩子之后

借助 nextTick 的特性,可以合理的完成需要依赖 DOM 的操作

总结

  1. 一次只做一件事,一次做好一件事(js 单线程特性)
  2. 宏任务和微任务形成的队列一次循环便是事件循环
  3. vue 的 $nextTick 机制可以理解成事件循环和防抖的结合