美文网首页让前端飞
vue 原理 Object.defineProperty 如何监

vue 原理 Object.defineProperty 如何监

作者: 小7丁 | 来源:发表于2019-03-23 22:49 被阅读2次

Object.defineProperty(obj,prop,descriptor)

  • 参数
    obj: 目标对象
    prop: 需要定义的属性或方法的名称
    descriptor:目标属性所拥有的特性

  • 可供定义的特性列表
    value: 属性的值
    writable: 如果为false,属性的值就不能被重写。
    get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
    set: 一旦目标属性被赋值,就会调回此方法。
    configurable: 如果为false,则任何尝试删除目标属性或修改属性性以下特性(writable, configurable, enumerable)的行为将被无效化。
    enumerable: 是否能在for...in循环中遍历出来或在Object.keys中列举出来。


源码:

function defineReactive (obj, key, val) {
  // 生成一个新的Dep实例,这个实例会被闭包到getter和setter中
  var dep = new Dep()

  var property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get
  var setter = property && property.set
  // 对属性的值继续执行observe,如果属性的值是一个对象,那么则又递归进去对他的属性执行defineReactive
  // 保证遍历到所有层次的属性
  var childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val
      // 只有在有Dep.target时才说明是Vue内部依赖收集过程触发的getter
      // 那么这个时候就需要执行dep.depend(),将watcher(Dep.target的实际值)添加到dep的subs数组中
      // 对于其他时候,比如dom事件回调函数中访问这个变量导致触发的getter并不需要执行依赖收集,直接返回value即可
      if (Dep.target) {
        dep.depend()
        if (childOb) {
         //如果value是对象,那就让生成的Observer实例当中的dep也收集依赖
          childOb.dep.depend()
        }
        if (isArray(value)) {
          for (var e, i = 0, l = value.length; i < l; i++) {
            e = value[i]
            //如果数组元素也是对象,那么他们observe过程也生成了ob实例,那么就让ob的dep也收集依赖
            e && e.__ob__ && e.__ob__.dep.depend()
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val
      if (newVal === value) {
        return
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // observe这个新set的值
      childOb = observe(newVal)
      // 通知订阅我这个dep的watcher们:我更新了
      dep.notify()
    }
  })
}

Dep类的定义极其简单,一个id,一个数组,他就是一个很基本的发布者-观察者模式的实现,作为一个发布者,他的subs属性用来存放了订阅他的观察者,也就是后面我们会说到的watcher。

在dep的订阅者数组中存放了Dep.target,让Dep.target订阅dep

那Dep.target是什么?他就是我们后面介绍的Watcher实例,为什么要放在Dep.target里呢?是因为getter函数并不能传参,dep可以通过闭包的形式放进去,那watcher可就不行了,watcher内部存放了a+b这个表达式,也是由watcher计算a+b的值,在计算前他会把自己放在一个公开的地方(Dep.target),然后计算a+b,从而触发表达式中所有遇到的依赖的getter,这些getter执行过程中会把Dep.target加到自己的订阅列表中。等整个表达式计算成功,Dep.target又恢复为null.这样就成功的让watcher分发到了对应的依赖的订阅者列表中,订阅到了自己的所有依赖。

上面setter代码里的dep.notify就负责完成数据变动时通知订阅者的功能

getter和setter存在的缺陷:只能监听到属性的更改,不能监听到属性的删除与添加。

Vue的解决办法是提供了响应式的api: vm.$set / vm.$delete / Vue.set /
Vue.delete/ 数组的 $set/ 数组的 $remove

具体方法是为所有的对象和数组(只有这俩哥们才可能delete和新建属性),也创建一个dep,也完成收集依赖的过程。我们回到源码defineReactive再看一遍,在执行defineReactive(data,'a',{b:true})时,他首先创造了那个闭包在getter/setter中的dep,然后var childOb = observe(val),val是{b:true},那就会为这个对象new Observer(val),并放在val.ob上,而这个ob实例上存放了一个Dep实例。现在我们看到,有两个Dep实例,一个是闭包里的dep,一个是为{b:true}创建的ob上的这个dep。而:class="a"生成的watcher的求值过程中会触发到a的getter。

参考:https://github.com/Ma63d/vue-analysis/issues/1

相关文章

网友评论

    本文标题:vue 原理 Object.defineProperty 如何监

    本文链接:https://www.haomeiwen.com/subject/tvfhvqtx.html