前备知识
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
线程
- 执行
- 进入下一次循环