「源码剖析」NextTick到底有什么作用

时间:2022-01-23 06:50:53

「源码剖析」NextTick到底有什么作用

vue中每次监听到数据变化的时候,都会去调用notify通知依赖更新,触发watcher中的update方法。

  1. update () { 
  2.    /* istanbul ignore else */ 
  3.    if (this.lazy) { 
  4.      
  5.    } else if (this.sync) { 
  6.    
  7.    } else { 
  8.      this.get() 
  9.      //queueWatcher(this) 
  10.    } 
  11.  } 

如果通过watcher中的get方法去重新渲染组件,那么在渲染的过程中假如多次更新数据会导致同一个watcher被触发多次,这样会导致重复的数据计算和DOM的操作。如下图所示,修改3次message之后DOM被操作了3次。

「源码剖析」NextTick到底有什么作用

为了解决上述问题,不去直接调用get方法而是将每次调用update方法后需要批处理的wather暂存到一个队列当中,如果同一个 watcher 被多次触发,通过wacther 的id属性对其去重,只会被推入到队列中一次。然后,等待所有的同步代码执行完毕之后在下一个的事件循环中,Vue 刷新队列并执行实际 (已去重的) 工作。

  1. let has: { [key: number]: ?true } = {} 
  2. let waiting = false 
  3. export function queueWatcher (watcher: Watcher) { 
  4.   const id = watcher.id  //对watcher去重 
  5.   if (has[id] == null) { 
  6.     has[id] = true 
  7.     queue.push(watcher); 
  8.     if (!waiting) {  //节流 
  9.       waiting = true 
  10.       nextTick(flushSchedulerQueue) 
  11.     } 

调用watcher的run方法异步更新DOM

  1. let has: { [key: number]: ?true } = {} 
  2. function flushSchedulerQueue () { 
  3.   let watcher, id 
  4.   queue.sort((a, b) => a.id - b.id) 
  5.  
  6.   for (index = 0; index < queue.length; index++) { 
  7.     watcher = queue[index
  8.     if (watcher.before) { 
  9.       watcher.before() 
  10.     } 
  11.     id = watcher.id 
  12.     has[id] = null  //清空id 
  13.     watcher.run()   //更新值 
  14.   } 
  15.    
  16.   resetSchedulerState()   //清空watcher队列 
  17.  
  18. function resetSchedulerState () { 
  19.   index = queue.length  = 0 
  20.   has = {} 
  21.   waiting =  false 

在vue内部调用nextTick(flushSchedulerQueue),vm.$nextTick方法调用的也是nextTick()方法

  1. Vue.prototype.$nextTick = function (cb) { 
  2.    nextTick(cb,this); 
  3.  }; 

那么多次调用nextTick方法是怎么处理的呢?

  1. const callbacks = [] 
  2. let pending = false  
  3. export function nextTick (cb?: Function, ctx?: Object) { 
  4.   callbacks.push(() => { 
  5.     if (cb) { 
  6.       try { 
  7.         cb.call(ctx) 
  8.       } catch (e) { 
  9.         handleError(e, ctx, 'nextTick'
  10.       } 
  11.     } 
  12.   }) 
  13.   if (!pending) { 
  14.     pending = true 
  15.     timerFunc() 
  16.   } 

nextTick将所有的回调函数暂存到了一个队列中,然后通过异步调用更新去依次执行队列中的回调函数。

  1. function flushCallbacks () { 
  2.   pending = false 
  3.   const copies = callbacks.slice(0) 
  4.   callbacks.length = 0 
  5.   for (let i = 0; i < copies.length; i++) { 
  6.     copies[i]() 
  7.   } 

 「源码剖析」NextTick到底有什么作用

nextTick函数中异步更新对兼容性做了处理,使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

Promise

  1. if (typeof Promise !== 'undefined' && isNative(Promise)) { 
  2.   const p = Promise.resolve() 
  3.   timerFunc = () => { 
  4.     p.then(flushCallbacks) 
  5.   } 

 「源码剖析」NextTick到底有什么作用

MutationObserver

MutationObserver 它会在指定的DOM发生变化时被调用。创建了一个文本DOM,通过监听字符值的变化,当文本字符发生变化的时候调用回调函数。

  1. if (!isIE && typeof MutationObserver !== 'undefined' && ( 
  2.   isNative(MutationObserver) || 
  3.   MutationObserver.toString() === '[object MutationObserverConstructor]' 
  4. )) { 
  5.   let counter = 1 
  6.   const observer = new MutationObserver(flushCallbacks) 
  7.   const textNode = document.createTextNode(String(counter)) 
  8.   observer.observe(textNode, { 
  9.     characterData: true 
  10.   }) 
  11.   timerFunc = () => { 
  12.     counter = (counter + 1) % 2 
  13.     textNode.data = String(counter) 
  14.   } 

 「源码剖析」NextTick到底有什么作用

setImmediate

setImmediate该方法用作把一些需要持续运行的操作放在一个其他函数里,在浏览器完成后面的其他语句后,就立即执行此替换函数。

  1. if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { 
  2.   timerFunc = () => { 
  3.     setImmediate(flushCallbacks) 
  4.   } 
  5. }else
  6.   timerFunc = () => { 
  7.     setTimeout(flushCallbacks, 0) 
  8.   } 

 「源码剖析」NextTick到底有什么作用

总结

vue渲染DOM的时候触发set方法中的去依赖更新,在更新的过程中watcher不是每次都去执行去触发DOM的更新,而是通过对wather的去重之后,通过nextTick异步调用触发DOM更新。

nextTick()就是一个异步函数,在异步函数中通过队列批处理nextTick传入的回调函数cb,但是队列彼此不是同时进行的,通过节流的方式依次执行。

原文地址:https://mp.weixin.qq.com/s/UGk4-lOk0nLyCg5Pno1HQA