前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「源码剖析」nextTick到底有什么作用

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

作者头像
小丑同学
发布2021-02-08 09:25:14
2890
发布2021-02-08 09:25:14
举报
文章被收录于专栏:小丑的小屋小丑的小屋

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

代码语言:javascript
复制
 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 刷新队列并执行实际 (已去重的) 工作。

代码语言:javascript
复制
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

代码语言:javascript
复制
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()方法

代码语言:javascript
复制
 Vue.prototype.$nextTick = function (cb) {
    nextTick(cb,this);
  };

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

代码语言:javascript
复制
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将所有的回调函数暂存到了一个队列中,然后通过异步调用更新去依次执行队列中的回调函数。

代码语言:javascript
复制
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

代码语言:javascript
复制
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
  }
}

MutationObserver

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

代码语言:javascript
复制
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该方法用作把一些需要持续运行的操作放在一个其他函数里,在浏览器完成后面的其他语句后,就立即执行此替换函数。

代码语言:javascript
复制
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,但是队列彼此不是同时进行的,通过节流的方式依次执行。

END

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小丑的小屋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档