前备知识
Event Loop相关知识Promise有三种状态,一旦决议就不再发生改变,对已决议的Promise对象添加then回调函数,会立即将此回调放入微任务队列,等待事件循环执行(不同于Event:如果你错过了它,再去监听,是得不到结果的。)(参考)JS修改数据是同步的,JS修改Dom也是是同步的。Vue中修改数据是同步的,但DOM修改是异步的。-
异步修改的原因:避免不必要的计算和dom操作(参考)
-
异步修改的方式:将所有的
DOM操作缓存到一个队列中,再在异步函数的回调中依次执行 -
异步修改的时机:
- 如果环境支持
Promise:构造已决议的Promise,将队列放到then回调中,在当前循环的微任务阶段执行 - 不支持
Promise但支持MutationObserver:构造节点,监听节点变化并将队列任务放到回调中,手动修改节点,这样监听函数也会在当前循环的微任务中执行 - 以上均不支持,将队列任务放入
setImmediate回调,在当前循环后的第n个宏任务中执行 - 以上均不支持,将队列任务放入
setTimeout|0回调,在当前循环后的第n个宏任务中执行
- 如果环境支持
-
异步修改的实现:修改完数据之后,将修改dom的操作放入
nextTick的回调中,等待异步触发
-
nextTick(cb)的实现
- 将回调函数放入一个全局队列
- 如果是第一次放入,将队列注册到异步回调中,注册方式同 [
前备知识->3->异步修改时机] - 在当前事件循环的微任务中或当前循环后的第n个宏任务中触发回调
PS
- 如果在
nextTick调用之后,再改变响应式数据(如下),在nextTick回调中,通过DOM访问不到更新的值,因为手动调用nextTick在前,会被先放入队列,先执行,响应式数据的修改DOM操作还未执行,此时DOM还没更新,但是数据已经更新
this.$nextTick(() => {
console.log(this.$refs.message.innerText) // 此处打印为 old-value, 因响应式数据触发的dom操作还未执行
console.log(this.message) // 此处打印为 new-value
})
this.message = 'new value'
nextTick的回调函数执行时,只能(一定程度)保证在DOM为最新的DOM,但是页面可能还是原页面,渲染并没有完成,因为渲染工作是在微任务之后进行的JS引擎线程和渲染引擎线程是互斥的(虽然是两个线程,但是会互相阻塞)Event Loop运行机制是一个单独的线程,用于各线程之间通信,维护了一些任务队列- 一次事件循环
- 先拿一个宏任务的回调,执行同步代码(可能没有)
- 检查微任务队列,执行,清空
- 唤起GUI线程(可能多次循环唤起一次)
- 执行
requestAnimationFrame所有回调 - 再次清空微任务(由
GUI线程执行,队列是由Event Loop维护的) - 渲染页面
- 退出
GUI线程
- 执行
- 进入下一次循环