nexttick源码
[TOC]
异步更新
1 要实现什么?
用户可能连续几行代码中,多次修改同一变量数据。若每次修改,都进行dom更新,显然是性能浪费
fix:将DOM更新操作进行延迟,也就是把DOM更新操作暂存起来。等同步代码执行完后,再进行异步的DOM更新(可以说使用了promise)
2 将DOM更新操作进行延迟
如何将DOM更新延迟呢
修改数据后,会进入 watcher 的 update() 方法
然后执行 queueWatcher() 方法
1 | /** |
3 queueWatcher()
首先要把当前watcher暂存起来
暂存这里,就引出了几个问题:
- watcher 存到哪里?
- 可以直接存储吗?
Answer:
- 它维护了一个队列:queue,将 watcher 维护在这里
- 不可以直接存储,因为用户可能连续几行代码中,多次修改同一变量数据而触发 watcher 的 update(),所以要对 watcher 进行去重,在加入到队列 queue
到这里,暂存就搞定了
那么就可以进行刷新渲染操作了
刷新操作关键点是:vue 希望不论 watcher 的 update 执行多少次,它的刷新渲染操作仅执行一次
因此,这里使用了一个锁机制:waiting
1 | let waiting = false |
初始化的时候,waiting = false
因此第一次,会执行 if 语句的代码, 执行异步渲染操作
后面即使多次触发 queueWatcher ,也不会进入 if 语句的代码了
里面的代码,我在上面简述为:执行异步渲染操作
那么这个过程是什么呢?
其实就是:异步的、把 queue 中存储的 watcher 一个一个拿出来进行渲染操作
比如用计时器实现异步,传入一个方法做渲染操作:
1 | setTimeout( 执行渲染操作, 0 ) |
(当前,vue 中并不是简单的用 setTimeout 来实现的,后面再说)
具体来说:
执行渲染操作的方法叫做: flushSchedulerQueue()
它做了2件事情:
- 遍历 queue 中维护的 watcher,执行渲染逻辑
- 然后 waiting 置为 false,将锁打开
以上面为基础,举一个 DEMO:
用户在界面多次修改:
1 | vm.name = 'xx' |
执行第一行代码:
触发了 watcher 的 update,进入 queueWatcher(),vue 会将 这个 watcher 加入到 queue 中
此时锁是打开的,会进行异步渲染操作(因为它是异步的,所以不会立刻渲染),并将锁关上了
执行第一行、第三行代码:
虽然多次触发了 update,但是这个 watcher 已经加入了,所以不会重复加入,并且锁是关上的
同步代码执行完了,可以执行异步代码了
执行 flushSchedulerQueue()
会调用 watcher.run()
run() 实际上又执行力 get() 方法(watcher的通用求值方法),即
入栈;
执行回调(vm_update vm_render); // 这里拿到的值是 name 的最新值:’end’,进行渲染
出栈;
渲染完了,将锁打开
所以,即使多次修改值,异步渲染操作仅会执行一次,且拿到最新值
现在这样看起来,还挺完美
但如果执行下面这些代码:
1 | vm.name = 'icheng' |
此时,界面上的数据是新的,但是打印出来的数据,是旧的
因为:这两行同步代码会先执行,然后才会异步更新渲染,所以第二行去拿 dom 上的数据,这个数据根本还没更新
如何解决这个问题
1 | vm.name = 'icheng' |
引出了 vue 中一个核心方法:nextTick()
有了nextTick(),我们可以把前面的执行异步渲染操作的 setTimeout() 改为 nextTick()
1 | setTimeout( flushSchedulerQueue(), 0 ) |
👇
1 | nextTick( flushSchedulerQueue(), 0 ) |
nextTick 中,会传入 flushSchedulerQueue() 渲染更新,也会传入用户定义的的方法,然后依次执行
因此,vue 中有一个 callbacks 队列,维护所有这些任务
4 nextTick()
首先将传入的任务,推入 callbacks 队列
然后,以异步的方式、执行任务
- 执行任务指的是:遍历 callbacks 队列按照顺序依次执行
所以,nextTick() 不是创建一个异步任务,而是将这个任务维护到了队列中,依次执行
- 那么 vue 是如何采用异步的方式呢?
考虑兼容性,它内部没有确定采用了哪个异步 API,而是采用了优雅降级的方式:
- 内部先采用的是 promise(ie不兼容)
- 不兼容则使用 MutationObserver(h5的api)
- 否则可以考虑ie专享的 setImmediate
- 或者 setTimeout
源码
nextTick() 源码:
1 | export function nextTick(cb?: (...args: any[]) => any, ctx?: object) { |
优雅降级的源码:
1 | // 注:flushCallbacks是遍历 callbacks 队列按照顺序依次执行 |
queueWatcher 源码:
1 | /** |
About this Post
This post is written by Duan WeiCheng, licensed under CC BY-NC 4.0.