$nextTick原理

Vue官网对数据操作是这么描述的

能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

this.$nextTick,在我的感觉中,就相当于一个延迟处理吧.可以用来获取到数据更新之后的DOM

// Promise.resolve().then(() => {
      //   console.log(this.$refs.searchInput)
      //   this.$refs.searchInput.focus()
      // })
      // setTimeout(() => {
      //   console.log(this.$refs.searchInput)

      //   this.$refs.searchInput.focus()
      // }, 0)
      this.$nextTick(() => {
        console.log(this.$refs.searchInput)
        this.$refs.searchInput.focus()
      })

 

源码分析

当我们调用了.this.nextTick这个函数之后,传入回调函数,这个回调函数

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
       把$nextTick的回调函数(cb)放到了一个叫callbacks的一个数组里面,这个数组是在最上面定义的,
         如果你调用了多个this.$nextTick 数组里面就有了多个待执行的回调函数
        
const callbacks = []
let pending = false

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
flushCallbacks这个函数.如果调用他 它里面就会取出nextTick的回调函数来进行执行

 

而这个flushCallbacks 是写了一个判断.浏览器的降级处理.如果浏览器支持promise 就把这些会回调函数放到了promise.then里面 执行了否则调用后面的.做了四层判断

 

export let isUsingMicroTask = false
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  //判断1:是否原生支持Promise
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  //判断2:是否原生支持MutationObserver
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  //判断3:是否原生支持setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  //判断4:上面都不行,直接用setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

 

可以看出这边代码其实是做了四个判断,对当前环境进行不断的降级处理,尝试使用原生的Promise.thenMutationObserversetImmediate,上述三个都不支持最后使用setTimeout;降级处理的目的都是将flushCallbacks函数放入微任务(判断1和判断2)或者宏任务(判断3和判断4),等待下一次事件循环时来执行。

 

总结

到这里,整体nextTick的代码都分析完毕了,总结一下它的流程就是:

  1. 把回调函数放入callbacks等待执行
  2. 将执行函数放到微任务或者宏任务中
  3. 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调

 

myself总结:

实际上我有去看过这个$nextTick的源码,它里面的话是把这些.回调函数放到一个数组里面去,然后对浏览器做了一个兼容性的判断.好像一共四层判断吧.当浏览器支持promise的时候,这个回调函数就在promise的then(微任务)里面执行了.

否则会去判断MutationObserver和setImmediate 最后实在不行了才调用了SetTimeout(,0)(宏任务)来进行执行回调

鉴于以上的回答,可能牵连的面试:

1.事件循环机制

2.$nextTick是微任务还是宏任务

posted @ 2023-01-06 18:56  z_bky  阅读(48)  评论(0)    收藏  举报