$nextTick 的原理源码解读

  • Post author:
  • Post category:其他

$nextTick 的原理

Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM,但有时候业务需要必须对数据更改–刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback)这个 api 了。

nextTick 回调函数先后执行的方式。如果大家看过这部分的源码,会发现其中做了很多 isNative()的判断,因为这里还存在兼容性优雅降级的问题。可见 Vue 开发团队的深思熟虑,对性能的良苦用心。

工程源码: src/core/util/next-tick.js

// 储存 nextTick 的回调函数, 同一时期可能有多个地方会调用 $nextTick()
const callbacks = []
// 节流阀: 目的是同一时刻只开启一个 timerFunc
let pending = false 
// 定时函数
let timerFunc 

// 刷新队列的函数
function flushCallbacks () {
  pending = false
  // 复制 callback
  const copies = callbacks.slice(0)
  // 清除 callback
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
  	// 以此调用 nextTick 回调
    copies[i]()
  }
}

// $nextTick 会依次判断是否支持 Promise => MutationObserver => setImmediate => setTimeout
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // 如果支持 Promise, 就使用 Promise 实现
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
  }
} else if (!isIE && typeof MutationObserver !== 'undefined') {
  // 如果 Promise 不支持, 但支持 MutationObserver
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  // 改变 counter 属性触发 flushCallbacks
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 上面两种都不支持, 用 setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // 都不支持使用 setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

export function nextTick (cb, ctx) {
  // 存入 nextTick 回调, 可存入多个
  callbacks.push(() => {
    cb && cb.call(ctx)
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
}

总结

  • Vue 中 $nextTick 的降级策略
    Promise > MutationObserver > setImmediate > setTimeout
  • 原理
    • 利用异步队列
  • 在每个 macro-task(宏任务) 运行完以后, UI 都会重新渲染, 那么在 micro-task (异步事件回调) 中就完成数据更新当前事件循环结束就可以得到最新的 UI 了
  • 反之如果新建一个 macro-task 来做数据更新, 那么渲染就会进行两次

版权声明:本文为m0_62371650原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。