likes
comments
collection
share

vue中的nextTick源码解析

作者站长头像
站长
· 阅读数 22

nextTick 在 vue2 中

文档介绍

  • 参数

    • {Function} [callback]
    • {Object} [context]
  • 用法

    在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

    // 修改数据
    vm.msg = 'Hello'
    // DOM 还没有更新
    Vue.nextTick(function () {
       // DOM 更新了
    })
    // 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
    Vue.nextTick()
       .then(function () {
           // DOM 更新了
       }) 
    

    2.1.0 起新增:如果没有提供回调且在支持 Promise 的环境中,则返回一个 Promise。请注意 Vue 不自带 Promise 的 polyfill,所以如果你的目标浏览器不原生支持 Promise (IE:你们都看我干嘛),你得自己提供 polyfill。

源码解析

// 执行队列中的函数
function flushCallbacks() {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

let timerFunc
// promise为第一优先级,如果支持promise,则直接使用promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (
  !isIE &&
  // 如果支持MutationObserver,使用MutationObserver监听
  typeof MutationObserver !== 'undefined' &&
  (isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    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)) {
  // 第三优先级使用setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // 最后使用setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}


export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e: any) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  
  if (!pending) {
    pending = true
    timerFunc()
  }
  // 这里处理第二种调用方式
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

分块解析

promise

promise经常用来处理异步调用,属于微任务。在vue2中最优先选择的就是promise,在Promise.resolve().then()中执行的任务将进入微任务队列。

MutationObserver

MutationObserver可能用的比较少,它提供了监视对 DOM 树所做更改的能力。

在promise不可用时,使用了MutationObserver对新建的DOM进行监听,并通过修改DOM来触发事件进入微任务队列

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)
  }

setImmediate

setImmediate只在node中使用,属于宏任务。在事件循环机制中,他在check阶段进行执行。在一次正常的同层级的事件循环中,比setTimeout要快。

setTimeout

js中的老常客,延迟执行函数,属于宏任务,不设时间或者把时间设为0时就是为了将任务置入事件循环中,等下一个循环再执行。

nextTick 在 vue3 中

文档介绍

nextTick()

等待下一次 DOM 更新刷新的工具方法。

  • 类型

    function nextTick(callback?: () => void): Promise<void>
    
  • 详细信息

    当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

    nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。

  • 用法

async increment() {
    this.count++ // DOM 还未更新
    console.log(document.getElementById('counter').textContent) // 0 
    await nextTick() // DOM 此时已经更新
    console.log(document.getElementById('counter').textContent) // 1 
}

源码

const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
let currentFlushPromise: Promise<void> | null = null

export function nextTick<T = void>(
  this: T,
  fn?: (this: T) => void
): Promise<void> {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}

解析

在vue3中,nextTick就简单很多,只能通过promise创建微任务。这是因为放弃支持IE。在官方文档就写到一下部分

vue中的nextTick源码解析

转载自:https://juejin.cn/post/7239593701806440503
评论
请登录