vue中nextTick的理解以及如何实现

 在下次DOM更新循环结束之后执行回调 文件位置 core/utils/next-tick
异步更新原理
  data属性对应一个Dep
  Dep在数据访问get的时候触发Dep.depend搜集watcher
  Dep在更改数据的时候set触发Dep.notify便利触发watcher更新(此处vue会调用nextTick,所以dom更新是异步的)
// src > core > observer > watcher.js + scheduler.js// 当一个 Data 更新时,会依次执行以下代码
// 1. 触发 Data.set
// 2. 调用 dep.notify
// 3. Dep 会遍历所有相关的 Watcher 执行 update 方法
class Watcher {
  // 4. 执行更新操作
  update() {
    queueWatcher(this);
  }
}

const queue = [];

function queueWatcher(watcher: Watcher) {
  // 5. 将当前 Watcher 添加到异步队列
  queue.push(watcher);
  // 6. 执行异步队列,并传入回调
  nextTick(flushSchedulerQueue);
}

// 更新视图的具体方法
function flushSchedulerQueue() {
  let watcher, id;
  // 排序,先渲染父节点,再渲染子节点
  // 这样可以避免不必要的子节点渲染,如:父节点中 v-if 为 false 的子节点,就不用渲染了
  queue.sort((a, b) => a.id - b.id);
  // 遍历所有 Watcher 进行批量更新。
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    // 更新 DOM
    watcher.run();
  }
}

Vue并不会立即触发watcher跟新dom(算是性能优化)。而是把具体的方法传给nextTick的callback数组中,nextTick中包含watcher等更新dom方法以及用户调用nextTick的回调函数

nextTick执行timeFunc函数(适配浏览器promise => MutationObserve => setImmediate => setTimeout等异步任务),异步(promise等)回调执行flushCallbacks
nextTick方法
let pending = false   // 异步锁
nextTick (cb?: Function, ctx?: Object) {
  callbacks.push(() => {  // callback数组添加回调函数
    cb.call(ctx)
  })
  if (!pending) {     //  判断是否需要添加任务队列
    pending = true  // 关锁
    timerFunc()       // timeFunc为兼容浏览器的任务队列方法法promise,setTiemout等,回调执行flushCallbacks
  }
}

flushCallbacks方法

 const callbacks = []  // 回调队列
 function flushCallbacks () {
   pending = false    // 开锁
   const copies = callbacks.slice(0)  // 复制一份回调数组
   callbacks.length = 0                   // 清空原callback数组
   for (let i = 0; i < copies.length; i++) {
     copies[i]()
   }
 }

timeFunc主要是兼容浏览器

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

 nextTick为什么定义pending锁

  添加一个回调函数就关闭锁,此时处于执行完同步代码就执行异步代码的情况,这个变量作用是只开启一个异步任务,仍然可以继续添加回调函数,限制不开启多个异步更新队列

nextTick为什么复制一份callback数组,回调函数队列

  防止nextTick套用nextTick的情况,如果不做特殊处理会出现nextTick里面的nextTick提前进入任务队列,
  相当于下一个班车的乘客提前上了上一个班车(例子生动形象)
实现简易版的nextTick
 1 let pendding = false;
 2 let callback = []
 3 function flushCallback() {
  pedding = false  
 4   let copies = callback.slice(0)  // 复制一份callback
 5   callback.length = 0             // 清空callback
 6   for(var i = 0; i < copies.length; i++) {
 7     copies[i]()
 8   }
 9 }
10 function nextTick(cb) {
11   callback.push(cb)
12   if (!pendding) {   // 判断锁是否开着
13     pendding = true; // 关锁
14     setTimeout(flushCallback, 0)  // 添加一个异步任务
15   }
16 }

 

posted @ 2021-10-20 19:00  盐焗小羊腿  阅读(221)  评论(0)    收藏  举报