Vue的依赖收集简单图解

vue中利用Object.defineProperty定义getter/setter实现了数据的响应式,里面重要的一点就是依赖收集,它是计算属性和被依赖属性之间的桥梁,有了依赖收集(Dep),当被依赖对象A的getter函数执行时,所有依赖它的东东就会被收集到A的依赖库中,当A的setter执行时,依赖库中的东东就会被一个一个执行,通知依赖对象B。而这些被封装的依赖是在B的getter执行的时候注入到Dep的静态属性target中的

图很丑,包涵

/*
* 定义一个“依赖收集器”
* */
class Dep {
  constructor () {
    this.deps = []
  }

  /* 收集依赖 */
  depend () {
    if (Dep.target && this.deps.indexOf(Dep.target) === -1) {
      this.deps.push(Dep.target)
    }
  }

  /* 执行依赖 */
  notify () {
    this.deps.forEach((dep) => {
      dep()
    })
  }
}

/* target是被观察者的回调执行结果和计算属性被更新后调用的函数的封装 */
Dep.target = null

/* 把一个对象的每一项都转化成可观测对象 */
class Observable {
  constructor (obj) {
    return this.walk(obj)
  }

  walk (obj) {
    const keys = Object.keys(obj)
    keys.forEach((key) => {
      this.defineReactive(obj, key, obj[key])
    })
    return obj
  }

  /* 使一个对象转化成可观测对象 */
  defineReactive (obj, key, val) {
    const dep = new Dep()
    Object.defineProperty(obj, key, {
      get () {
        dep.depend()
        return val
      },
      set (newVal) {
        val = newVal
        dep.notify()
      }
    })
  }
}

/*
* 观察者
* obj: 被观察对象
* key: 被观察者key
* cb: 回调函数,返回“计算属性”的值
* onComputedUpdate: 当计算属性的值被更新时调用*/
class Watcher {
  constructor (obj, key, cb, onComputedUpdate) {
    this.obj = obj
    this.key = key
    this.cb = cb
    this.onComputedUpdate = onComputedUpdate
    return this.defineComputed()
  }

  defineComputed () {
    const self = this
    const onDepUpdated = () => {
      const val = self.cb()
      this.onComputedUpdate(val)
    }

    Object.defineProperty(self.obj, self.key, {
      get () {
        Dep.target = onDepUpdated
        const val = self.cb()
        Dep.target = null
        return val
      },
      set () {
        console.error('计算属性无法被赋值!')
      }
    })
  }
}

运行一下:

const hero = new Observable({
  health: 3000,
  IQ: 150
})

new Watcher(hero, 'type', () => {
  return hero.health > 4000 ? '坦克' : '脆皮'
}, (val) => {
  console.log(`我的类型是:${val}`)
})

console.log(`英雄初始类型:${hero.type}`)

hero.health = 5000

// -> 英雄初始类型:脆皮
// -> 我的类型是:坦克

参考资料:

https://zhuanlan.zhihu.com/p/29318017

猜你喜欢

转载自my.oschina.net/u/2510955/blog/1817177