本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

暂无数据

Vue 源码解析(二):依赖更新(Watcher对象,nextTick和更新队列)

发布于2021-05-30 12:11     阅读(1736)     评论(0)     点赞(3)     收藏(4)


第一篇,新建watcher对象时运行的getter函数,会调用data属性的get修饰器从而触发dep.depend()函数,完成了watcher与dep依赖的收集。让watcher和data建立了联系。那么这一次,让我们看一下依赖收集完成之后,是如何完成依赖更新的。

让我们从还是在initData() 方法里面的defineReactive()方法再次开始吧!

export function defineReactive (obj: Object, key: string, val: any, customSetter?: Function) {
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      ...
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)
      dep.notify() // <-- 依赖更新的重点
    }
  })
}

这次让我们看一下set修饰器的作用,在代码中出现 this.XXX = XXX 这样的语句时。set修饰器的方法就会被触发。首先通过getter得到旧值,判断旧值与新值是否相同,如果相同就不触发更新。然后使用新值替换旧值,再次递归观察新值,最后调用 dep.notify() 通知监听器依赖更新了。

export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;
  // 通知所有监听的watcher更新
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice() // 浅复制subs数组
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
export default class Watcher {
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
        this.dirty = true
    } else if (this.sync) { // 默认值是false
        this.run()
    } else {
        queueWatcher(this) // <-- 会加入watcher更新队列
    }
  }
}

这个notify()函数很简单,就是运行了subs数组里面的每个Watcher对象的update()函数,调用监听自己属性的所以监听器的更新函数。

而这个update()函数也很简单,根据是否同步走不同的方法,一般sync的默认值都是false,所以我们会走queueWatcher()函数。当然run()函数迟早也会运行的。所以我们就会看一下run()函数。

run () {
  if (this.active) {
    const value = this.get()
    if (
      value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      const oldValue = this.value
      this.value = value
      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "${this.expression}"`)
        }
      } else {
        this.cb.call(this.vm, value, oldValue)
      }
    }
  }
}

我们看到 run() 函数首先重新运行了this.get()函数得到了新的watcher监听的值。然后调用this.cb函数将Watcher的新值和旧值都传了进去。这个cb函数就是我们自己定义的监听函数,所以我们在写的时候可以使用新值和旧值去写一些我们自己的逻辑。

const queue: Array<Watcher> = []
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
      has[id] = true
      if (!flushing) {
          queue.push(watcher)
      } else {
          // if already flushing, splice the watcher based on its id
          // if already past its id, it will be run next immediately.
          let i = queue.length - 1
              while (i >= 0 && queue[i].id > watcher.id) {
              i--
          }
          queue.splice(Math.max(i, index) + 1, 0, watcher)
      }
      // queue the flush
      if (!waiting) {
          waiting = true
          nextTick(flushSchedulerQueue)
      }
  }
}

queueWatcher() 函数,如果没有在更新,则直接把watcher对象放入队列里面就行了。但是如果正在更新,上次我们讲过,watcher的更新顺序很重要,因为在watcher之间也存在依赖关系,所以后面的watcher是有可能依赖前面的watcher的。而决定watcher前后的就是id,所以使用在队列中比较id的大小,插入到适合的位置。

最后,调用flushSchedulerQueue函数以一定的周期(nextTick)更新队列。

function flushSchedulerQueue () {
  flushing = true
  let watcher, id, vm
  queue.sort((a, b) => a.id - b.id)  // 1、重新排序

  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    id = watcher.id
    has[id] = null
    watcher.run() // 2、运行更新方法

    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > config._maxUpdateCount) {  // 3、判断是否触发循环更新
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }

  // reset scheduler before updated hook called
  const oldQueue = queue.slice()
  resetSchedulerState()  // 4、重置更新队列

  // call updated hooks
  index = oldQueue.length
  while (index--) {
    watcher = oldQueue[index]
    vm = watcher.vm
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated') // 5、调用updated钩子
    }
  }
}
function resetSchedulerState () {
  queue.length = 0
  has = {}
  if (process.env.NODE_ENV !== 'production') {
    circular = {}
  }
  waiting = flushing = false
}

更新队列的 flushSchedulerQueue 函数一共有5步。分别是先对watcher进行排序,然后循环调用watcher的run()函数,完成更新。同时判断是否循环更新, config._maxUpdateCount 的值是100,接着更新完之后,会重置更新队列。最后判断 更新的watcher里面是否有视图的watcher对象,如果有调用 updated 钩子。




所属网站分类: 技术文章 > 博客

作者:Jjxj

链接:http://www.qianduanheidong.com/blog/article/116088/5b5e279f4962454853e2/

来源:前端黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

3 0
收藏该文
已收藏

评论内容:(最多支持255个字符)