欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue关于$nextTick源码的学习笔记

程序员文章站 2022-03-28 11:13:27
...

我们都知道Vue在视图更新采用的是异步更新策略,简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件中的所有数据变化完成之后,再统一进行视图更新。

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

说到更新,那肯定要提到 Watcher,我们来看源码:

Watcher:

// 以下是我精简过的代码
// 初始化时
this.deep = this.user = this.lazy = this.sync = false
...
  update () {
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
...

queueWatcher

也就是当触发update更新的时候,会去执行queueWatcher方法:


let has = {};
let queue = [];
let waiting = false;
let flushing= false;

// queueWatcher函数 部分代码
export function queueWatcher (watcher: Watcher) {
  // 防止queue队列wachter对象重复
  // 意思就是 去重 实现同一个数据多次修改,只触发一个Watcher
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}

// flushSchedulerQueue函数 部分代码
function flushSchedulerQueue () {
    let watcher, id;
    for (index = 0; index < queue.length; index++) {
        watcher = queue[index];
        id = watcher.id;
        has[id] = null;
        // 执行更新
        watcher.run();
    }
    // 更新完毕恢复标志位
    waiting = false;
}
  1. queue 里面存放着我们本次要更新的watcher对象,queueWatche r函数做了一个去重操作,相同的 watcher 对象只会被加入到queue队列一次
  2. flushSchedulerQueue 函数依次调用了wacther对象的run方法执行更新,并作为回调传递给了nextTick函数。
  3. waiting 这个标记位代表我们是否已经向 nextTick 函数传递了更新任务,nextTick 会在当前更新任务结束后再去处理传入的回掉,只需要传递一次,更新完毕再重置这个标志位。
  4. waiting 变量,这是很重要的一个标志位,它保证 flushSchedulerQueue 回调只允许被置入callbacks一次。

$nextTick实现:


let callbacks = [];
let pending = false;
let timerFunc;

/**----- nextTick -----*/
function nextTick (cb) {
    // 把传进来的回调函数放到callbacks队列里
    callbacks.push(cb);

    // pending代表一个等待状态 等这个tick执行
    if (!pending) {
        pending = true
        timerFunc()
    }
    
    // 如果没传递回调 提供一个Promise化的调用
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
    }
}

/**----- timerFunc ----*/

// 1、优先考虑Promise实现
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]')) {
// 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)
  }
}

function flushCallbacks () {
  // 将callbacks中的cb依次执行
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
  1. 首先Vue通过callback数组来模拟事件队列,事件队里的事件,通过nextTickHandler方法来执行调用,而何时进行执行,是由timerFunc来决定的。
  2. timerFunc 是实现的核心,它会优先使用Promise等microtask,保证在同一个事件循环里面执行,这样页面只需要渲染一次。
  3. Promise --> MutationObserver--> setImmediate --> setTimeout,vue在这里用了降级处理的策略。
相关标签: Vue深入学习