实现一个最小版本vue(二)之observer

observer

功能

  • 负责把data选项中的属性转换成响应式数据

  • data中的某个属性也是对象,把该属性转换成响应式数据。

  • 数据变化发出通知

  • 结构

实现思路

  1. walk方法判断传入的data是否是对象,加强代码健壮性
  2. 遍历data对象所有属性,调用defineReactive,转化为响应式数据
  3. defineReactive需要传3个参数,第三个参数直接把val传入,而不是通过obj[key]获取,是因为,访问实例属性时,首先触发vue.js里的get方法,这个get调用了data[key],这里会触发observer里的get方法,如果在observer里的get里直接使用obj[key],则又触发了这个get方法,产生一个死递归,会发生堆栈溢出错误
  4. 这第三个参数val是局部对象,执行完成应该被释放,这里没有被释放,原因是:传入的objdata对象($data),而$data引用了get方法,外部引用了这个方法,而get 又用到了val,所以不会释放掉,产生了闭包
  5. 如果data对象里还包含对象,需要在defineReactive开始时也调用一次walk,判断内部是否还有嵌套,把嵌套对象也转换成响应式
  6. 修改data中的值,修改后的值也需要是响应式的,所以在set方法里,也调用一次walk

代码

class Observer {
  constructor (data) {
    this.walk(data)
  }

  walk (data) {
    // 1.判断data是否时对象
    if (!data || typeof data !== 'object') {
      return
    }
    // 2.遍历data对象所有属性
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key])
    })
  }

  // 定义响应式数据
  defineReactive (obj, key, val) {
    // 如果val此时是对象,会把val内部的属性转换成响应式的
    this.walk(val)
    const that = this
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get () {
        // 收集依赖
        Dep.target && dep.addSub(Dep.target)
        // 如果使用obj[key],则又触发了get方法,死递归,会发生堆栈溢出错误
        // return obj[key]
        return val
      },
      set (newValue) {
        if (newValue === val) {
          return
        }
        val = newValue
        // 新值也要通过walk进行响应式转换
        that.walk(newValue)
        // 发送通知
        dep.notify()
      },
    })
  }
}
posted @ 2020-07-07 12:59  Evo1uti0n  阅读(219)  评论(0编辑  收藏  举报