vue源码理解——什么是依赖

vue将数据变的可观测了以后,我们就知道了数据什么时候进行了改变,当数据改变的时候就去更新视图,但是去更新哪个视图呢,如果改变了一个数据,就去更新整个视图,明显这样是不合理的。

最正确的方法就是——哪个视图用了这个变化的数据,哪个视图就进行更新。

哪个视图用到了这个数据,也可以解读为哪个视图依赖了这个数据。

那么,谁用了这个数据谁就是依赖。

1、收集依赖

在vue中,为每个数据创建了一个数组来存放依赖。谁用到了这个数据,就将谁存放 到这个数组里(这就是依赖收集),当数据变化时,通知数组中所有的依赖进行更新。

在数据观测中,我们可以观测到数据的读取和修改。而数据的变化对应依赖的变化,所以对于依赖的收集我们可以总结为:在getter中收集依赖,在setter中通知依赖更新

function defineReactive (obj,key,val) {
  if (arguments.length === 2) {
    val = obj[key]
  }
  if(typeof val === 'object'){
    new Observer(val)
  }
  const dep = new Dep()  // 依赖管理器
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get(){
      dep.depend()// 在getter中收集依赖
      return val;
    },
    set(newVal){
      if(val === newVal){
          return
      }
      val = newVal;
      dep.notify()// 在setter中通知依赖更新
    }
  })
}

对于以上代码,我们可以看到,在get中通过dep.depend()来收集依赖,使用dep.notify()来更新依赖。那么,什么是dep呢?

2、依赖管理器Dep
之前我们说过,vue为每个数据创建了一个存放依赖的数组,但是单用一个数组来存放依赖,功能有些欠缺,可以将这个功能扩展一下,所以vue为每一个数据建立了一个依赖管理器,把这个数据所有的依赖都管理起来。

依赖管理器主要实现的功能
  1、添加一个依赖
  2、删除一个依赖
  3、通知所有依赖更新

export default class Dep {
  constructor () {
    this.subs = []
  }

  addSub (sub) {
    this.subs.push(sub)
  }
  // 删除一个依赖
  removeSub (sub) {
    remove(this.subs, sub)
  }
  // 添加一个依赖
  depend () {
    if (window.target) {
      this.addSub(window.target)
    }
  }
  // 通知所有依赖更新
  notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

export function remove (arr, item) {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}
3、watcher类

  在vue中还实现了一个watcher类,为什么把watcher类放在依赖这里讲呢?
  回到标题的问题:什么是依赖?
  答:谁用到了数据(发生变化),谁就是依赖,我们就为谁创建一个watcher实例。所以说,Watcher类的实例就可以称为依赖。

export default class Watcher {
  constructor (vm,expOrFn,cb) {
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn)
    this.value = this.get()
  }
  get () {
    window.target = this;
    const vm = this.vm
    let value = this.getter.call(vm, vm)
    window.target = undefined;
    return value
  }
  update () {
    const oldValue = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldValue)
  }
}

简单分析一下逻辑:
1、watcher实例化时先执行其构造函数,调用了this.get()方法
2、get()方法中,首先把实例自身赋给了全局对象window.target,然后通过this.getter.call获取一下被依赖的数据,目的是触发数据上面的getter()方法。getter方法会进行依赖收集,执行dep.depend()。而dep.depend()会取window.target上的值放入依赖数组。
3、数据变化时,触发数据的setter,并执行了dep.notify(),在dep.notify()遍历所有依赖(也就是watcher实例),并执行实例中的update()方法,更新视图。

更多干货,我们下篇见!

你可能感兴趣的:(vue.js,前端,javascript,typescript,学习,架构)