深入浅出Vue.js读后总结-vm.$nextTick

332 阅读1分钟

一、nextTick简介

nextTick作用:vm.$nextTick和Vue.nextTick一样,接受一个回调函数,将回调函数延迟到下次DOM更新周期之后执行。

顺便提一下,在Vue中,状态发生变化时,watcher会得到通知,然后触发虚拟DOM渲染,而watcher触发渲染这个操作是使用异步队列的,需要渲染时把watcher放入异步队列(添加前会判断是否有相同的watcher,不存在再添加),下一次事件循环中让watcher触发渲染并且清空队列。

所以为什么要用异步更新队列呢?

因为vue2的通知只通知到组件,组件内部再进行虚拟DOM比对更新DOM,所以当一个组件内有两个状态发生了变化,如果不用异步更新的话,整个组件就要更新两次,但其实总的来说只要更新一次组件就可以,所以只需要等两次状态更新完毕之后,再更新一次组件就可以。

二、下次DOM更新周期到底是什么时候

然后我们回到nextTick,nextTick中所说的下次DOM更新周期到底是什么时候呢?

nextTick其实是将回调函数放入微任务中,只有特殊情况才会降级放入宏任务。

所以使用nextTick获取更新后的DOM需要注意顺序问题,因为更新DOM的回调也是微任务,所以要先更新DOM,再使用nextTick获取更新后的DOM

三、nextTick实现原理

vm.$nextTick和Vue.nexTick功能都来自nextTick方法:

直接上nextTick()源码:

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  //这里先把nextTick里面传来的cb参数push到callbacks里面
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } 
    //如果cb不存在且支持promise就返回一个promise
    else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    //pending为false则将其赋值为ture,然后运行timeFunc函数
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

nextTick()主要功能就是把所有传进来的cb放再callbacks数组里面(cb不存在环境支持promise则返回一个promise),然后执行timeFunc()方法.

timeFunc()的主要功能就是将flushCallback方法放入微任务,如果有promise就用then方法放进去,没有promise就用MutationObserve,没有MutationObserve就用setImmediate,没有setImmediate就用setTimeout.

flushCallback()主要功能就是循环执行callbacks里面的回调函数,最后清空callbacks数组.

flushCallback()源码:

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

其中pending用来控制只能向任务队列中添加一个任务(这个任务就是flushCallback)