前言
js的事件循环机制对于我们理解阅读代码的执行顺序是必不可少的知识点。在 vue 中虚拟 DOM 对比之后的重渲染便是参考这种机制
Event Loop
js 是单线程语言,干完一件事之后才能干下一件事
js 执行的代码可分为同步和异步,同步代码执行完毕再执行异步任务
异步任务分为宏任务和微任务
执行同步代码
执行微任务队列
执行宏任务队列
重复 2 => 3 过程,直到没有任务队列
微任务
promise 就是典型的微任务
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 的函数,就是要执行的微任务
微任务有
MutationObserver // 检测 DOM,DOM3
Object.observe // 已经废弃 Object.defineProperty 和 Proxy 都是同步的
process.nextTick // node.js 独有,node.js中优先级高于 promise
宏任务
setInterval 和 setTimeout 是两个常见的任务
javascript复制代码console.log(1)
setTimeout(() => {
console.log(2)
})
console.log(3)
上面的执行结果
复制代码1 3 2
当 setTimeout 执行完毕的时候,会把 () => { console.log(2) } 作为宏任务放入宏任务队列
再来看个例子,setTimeout 的执行可能和预设的时间不一致
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
宏任务有
setTimeout
setInterval
setImmediate // node.js 独有,优先级低于 setTimeout
vue 中的应用
vue 更新 DOM 是异步更新的,更新的内容会形成一个队列,这个队列是去重的
ini复制代码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 的操作
总结
宏任务和微任务形成的队列一次循环便是事件循环
vue 的 $nextTick 机制可以理解成事件循环和防抖的结合