4

The source code of this api has been read a long time ago, but there has been no summary, so here is a summary.

How is this api used

nextTick in Vue. One is used as a global method Vue.nextTick , and the other is mounted on a component instance and used by vm.$nextTick . When called as an instance method, the callback this automatically bound to the instance that called it.

parameter:

// {Function} [callback]
// {Object} [context]
Vue.nextTick([callback, context])

usage:

The delayed callback is executed after the next DOM update cycle ends. Use this method immediately after modifying the data to get the updated DOM.

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

New since 2.1.0: If no callback is provided and in an environment that supports Promise, a Promise will be returned.

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

Source code analysis

Vue.nextTick is in this directory:

node_modules/vue/src/core/util/next-tick.js

The code here is recommended to start at line 33:

let timerFunc
if (typeof Promise !== 'undefined' && isNative(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]'
)) {
  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)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

The logic of this part is very simple, that is, environment , and then checks Promise -> MutationObserver -> setImmediate -> setTimeout exists, and if it exists, use it to determine which API the callback function queue is asynchronous with. implement.

Then look at the method definition nextTick

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

In the above code, it is slightly more complicated because it needs to implement the new logic of returning Promise. You can directly look at the simplified logic below:

export function nextTick (cb?: Function, ctx?: Object) {
  callbacks.push(() => {
    try {
      cb.call(ctx)
    } catch (e) {
      handleError(e, ctx, 'nextTick')
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
}

When the nextTick function receives a callback function, it does not call it first, but pushes it to a global callbacks array. In nextTick Finally, a method called timerFunc method, which is actually just sniff the environment when selected api, to Promise for example, timerFunc method should be long like this:

const p = Promise.resolve()
let timerFunc = () => {
  p.then(flushCallbacks)
  if (isIOS) setTimeout(noop)
}

After calling timerFunc , the flushCallbacks method will be executed in the next round of event loop. Let's look at the flushCallbacks method definition:

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 does is very simple. It executes all the methods in callbacks callbacks .

Attentive students here may ask, nextTick is the pending of 06103643993349 in 06103643993348. At first, it was false , then when calling nextTick , it was changed to true , and finally when flushCallbacks executed, it was changed to false , which seemed useless at first glance. . In fact, otherwise, we know that the nextTick method can be called multiple times in a synchronous method:

export default {
  methods: {
    handleClick() {
      this.$nextTick(() => {
        console.log(233);
      })
      this.$nextTick(() => {
        console.log(666);
      })
    }
  }
}

In the above code, the first nextTick called, the callback function is pushed into the callbacks array, and then timerFunc called to join the asynchronous queue. When the second nextTick called, the callback function is pushed into the callbacks array, because at this time pending already true , so timerFunc will not be executed repeatedly, that is, the asynchronous queue will not be added repeatedly. Only in the next round of event loop, after the functions in the asynchronous queue are executed, pending becomes false , then the asynchronous queue can be added again.

So to summarize, when the nextTick function receives a callback function, it does not call it first, but puts it in a global queue queue, and waits for the next round of the event loop to queue functions in sequence.

This queue may be the microTask queue, or it may be the macroTask queue. Promise and MutationObserver belong to the micro task queue, and setImmediate and setTimeout belong to the macro task queue.

refer to

Vue.nextTick-Vue official document


一杯绿茶
199 声望17 粉丝

人在一起就是过节,心在一起就是团圆