vue nextTick原理

nextTick用于在DOM更新后执行回调,它在数据变化后将更新函数加入队列,通过MutationObserver、Promise或setTimeout等方法在下一个事件循环执行,确保DOM已渲染完成。这一机制避免了频繁的DOM操作,优化了性能。
摘要由CSDN通过智能技术生成

介绍:
nextTick()是在下次 DOM 更新循环结束之后执行延迟回调。在修改数据后使用此方法来获取更新后的 DOM。

说明:
nextTick是在update更新的时候调用的,采用异步的方式更新DOM。当数据发生变化,Vue不会立刻更新DOM 而是开启一个队列把更新函数也就是Watcher放入队列中。当任务队列中同一个 Watcher 被多次触发,会避免重复只推入到队列中一次,然后刷新队列。接着在下一个事件循环中,nextTick()将这些更新函数以cb的形式放到任务队列的尾部,当事件循环结束且任务队列为空之后就会进行DOM渲染,DOM渲染完成后再去执行这些cb,也就拿到更新后的值。

原理:

update会调用queueWatcher(),queueWatcher()会把watcher实例添加到 queue,在添加之前做判重处理,会判断缓存对象has中是否已经存在该id,如果判断出 has[id] 不存在就push到queue,就不会每次改变数据都触发watcher。然后调用nextTick把消耗queue的flushSchedulerQueue 放到 callbacks队列,然后把消耗callbacks队列的flushCallback放入到下个事件循环当中,到下个事件循环会根据pending 来判断当前是否要执行timerFunc,若执行timerFunc则会调用flushCallback,timerFunc会根据当前环境判断使用哪种异步方式,优先使用依次是Promise(微),MutationObserver监视dom变动(微),setImmediate(宏),setTimeout(宏)。

【flushCallbacks函数的作用就是对所有callback进行遍历,然后指向响应的回调函数,也就是代码里写的nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。】

为什么要先把cb压入callbacks数组⽽不是直接在nextTick中执⾏cb?
起初将传入的回调函数cb(也就是flushSchedulerQueue)压入callbacks数组,最后通过timerFunc函数一次性解决。原因是保证能在同⼀个事件循环内多次执⾏nextTick,不会开启多个异步任务,⽽把这些异步任务都压成⼀个同步任务,在下⼀个事件循环执⾏完毕。

应用场景
1.create中想要获取dom时;
2.响应式数据变化后获取dom更新后的值;
在这里插入图片描述
源码:

/* @flow */
/* globals MutationObserver */

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

export let isUsingMicroTask = false

const callbacks = []
let pending = false

/**
 * 对所有callback进行遍历,然后指向响应的回调函数
 * 使用 callbacks 保证了可以在同一个tick内执行多次 nextTick,不会开启多个异步任务,而把这些异步任务都压成一个同步任务,在下一个 tick 执行完毕。
*/

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]( "i")
  }
}

let timerFunc

/**
* timerFunc 实现的就是根据当前环境判断使用哪种方式实现
* 就是按照 Promise.then和 MutationObserver以及setImmediate的优先级来判断,支持哪个就用哪个,如果执行环境不支持,会采用setTimeout(fn, 0)代替;
*/

// 判断是否支持原生 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
  // 不支持 Promise的话,再判断是否原生支持 MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // 新建一个 textNode的DOM对象,使用 MutationObserver 绑定该DOM并传入回调函数,在DOM发生变化的时候会触发回调,该回调会进入主线程(比任务队列优先执行)
  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
  // 不支持的 MutationObserver 的话,再去判断是否原生支持 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Technically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Promise,MutationObserver, setImmediate 都不支持的话,最后使用 setTimeout(fun, 0)
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

// 该函数的作用就是延迟 cb 到当前调用栈执行完成之后执行
export function nextTick (cb?: Function, ctx?: Object) {
  // 传入的回调函数会在callbacks中存起来
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // pending是一个状态标记,保证timerFunc在下一个tick之前只执行一次
  if (!pending) {
    pending = true
    /**
    * timerFunc 实现的就是根据当前环境判断使用哪种方式实现
    * 就是按照 Promise.then和 MutationObserver以及setImmediate的优先级来判断,支持哪个就用哪个,如果执行环境不支持,会采用setTimeout(fn, 0)代替;
    */
    timerFunc()
  }
  // 当nextTick不传参数的时候,提供一个Promise化的调用
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值