当前位置:首页 > IT科技

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

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

update () {     /* istanbul ignore else */    if (this.lazy) {     } else if (this.sync) {     } else {       this.get()      //queueWatcher(this)    }  } 

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

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

let has: {  [key: number]: ?true } = { } let waiting = false export function queueWatcher (watcher: Watcher) {    const id = watcher.id  //对watcher去重   if (has[id] == null) {      has[id] = true     queue.push(watcher);     if (!waiting) {   //节流       waiting = true       nextTick(flushSchedulerQueue)     } } 

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

let has: {  [key: number]: ?true } = { } function flushSchedulerQueue () {    let watcher, id   queue.sort((a, b) => a.id - b.id)   for (index = 0; index < queue.length; index++) {      watcher = queue[index]     if (watcher.before) {        watcher.before()     }     id = watcher.id     has[id] = null  //清空id     watcher.run()   //更新值   }   resetSchedulerState()   //清空watcher队列 } function resetSchedulerState () {    index = queue.length  = 0   has = { }   waiting =  false } 

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

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

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

const callbacks = [] let pending = false  export function nextTick (cb?: Function, ctx?: Object) {    callbacks.push(() => {      if (cb) {        try {          cb.call(ctx)       } catch (e) {          handleError(e, ctx, nextTick)       }     }   })   if (!pending) {      pending = true     timerFunc()   } } 

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

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

 

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

Promise

if (typeof Promise !== undefined && isNative(Promise)) {    const p = Promise.resolve()   timerFunc = () => {      p.then(flushCallbacks)   } } 

 

MutationObserver

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

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

 

setImmediate

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

if (typeof setImmediate !== undefined && isNative(setImmediate)) {    timerFunc = () => {      setImmediate(flushCallbacks)   } }else{    timerFunc = () => {      setTimeout(flushCallbacks, 0)   } } 

 

总结

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

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

分享到:

滇ICP备2023006006号-16