vue2源码解析之nextTick

589 阅读2分钟

一. 前言

一直都想在掘金写点东西, 但是总是害怕写得不好, 所以都是写了放在一个 private 的仓库里, 孤芳自赏. 今天早上起来, 花了一两个小时写的这篇算是一个开始(不敢写时间长, 自己看到漏洞或者问题多了, 就又不敢发了, 求饶过), 以后会努力多发一些文章, just do it.

二. 用法

vue 官网是这样介绍的. 地址

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。 其实这个方法就是可以一个回调函数放到事件循环的任务中去执行.

  1. 会在当前环境按照Promise>MutationObserver>setImmediate>setTimeout的顺序找到支持的任务, 可以看出优先使用微任务Promise, 如果不支持会一路降级到宏任务setTimeout
  2. 因为即使是在最快的Promise这个微任务中, 当前的同步任务(可以理解当前正在执行的代码是同步任务, 哪怕它是个宏任务)也已经执行完了, dom 就已经是更新了的了.
  3. 所以它的应用场景可以是为了等 dom 更新完成, 也可以是等当前同步代码执行完成.
  4. 等待 dom 更新完成, 比如想拿到修改了数据之后(此时的 dom 可能会被修改)的 dom 的宽高, 需要注意修改数据和 nextTick 的顺序, 下面会解释

三.写法

vue 官网是这样介绍的. 地址

// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
  // DOM 更新了
})

// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick().then(function () {
  // DOM 更新了
})

刚开始我还不理解为什么要加一个 Promise 的使用方法, 直到我看到了这种写法就理解了,完整代码

export default {
  methods: {
    updateMessage: async function () {
      this.message = '已更新'
      console.log(this.$el.textContent) // => '未更新'
      await this.$nextTick()
      console.log(this.$el.textContent) // => '已更新'
    },
  },
}

四.源码解析

完整版自己去 github 上看吧, 家里网络实在是打不开 github🤦🏻‍♀️

/**
 *
 * @param cb 要添加的回调函数
 * @param ctx 添加回调函数执行时的上下文this
 * @returns Promise | void
 */
export function nextTick(cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      cb.call(ctx)
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise((resolve) => {
      _resolve = resolve
    })
  }
}

内部使用到的变量

  • callbacks

    用来缓存当前一个 tick 添加的回调, 就是在当前同步代码中添加的所有回调都会保存在这个数组中.

  • pending

    用来标识是否正在执行回调, 就是有可能在回调里面又添加了回调

    如果pendingtrue时, 表示回调队列正在执行, 此时只是把当前的回调放入了回调队列, 不再执行新的回调队列callbacks

  • timerFunc

    这个是回调执行的任务函数(Promise>MutationObserver>setImmediate>setTimeout)四个中的某一个, 可以就认为它是Promise.prototype.then,它会把callbacks中保存的回调都拿出来, 执行之后, 清空callbacks

    执行的时机, 通过nextTick添加回调时, 此时没有上一个回调队列正在执行.

五.使用情况

在 vue 源码中, 它在三个地方被使用了

Vue.nextTick

Vue.nextTick = nextTick

$nextTick

Vue.prototype.$nextTick = function (fn: Function) {
  return nextTick(fn, this)
}

nextTick(flushSchedulerQueue)

这个是queueWatcher中执行的回调, 这个是数据绑定中的点了, 这里就不谈论了, 就是当vue实例数据更新会触发的回调.

export default {
  methods: {
    updateMessage: async function () {
      this.$nextTick(() => {
        console.log(this.$el.textContent) // => '未更新'
      })
      this.message = '已更新'
      this.$nextTick(() => {
        console.log(this.$el.textContent) // => '已更新'
      })
    },
  },
}

所以要注意this.$nextTick书写的位置呀

六.完

不写点什么了, 赶紧点发布......