vue2源码学习 (5).响应式原理-3.observe

223 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 9 天,点击查看活动详情

5.响应式原理-3.observe

start

  • 开始阅读 observe 相关的源码。
  • Vue 项目针对响应式相关的代码,都放在了 \src\core\observer 目录下。

1. observe

\src\core\observer\index.js

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
/**
 * 1.
 * 尝试为一个值创建一个观察者实例
 * 如果成功观察,返回新的观察者
 * 如果值已经有一个,返回现有的观察者,。
 */

// 2. 调用的时候 observe(data, true /* asRootData */);
export function observe(value: any, asRootData: ?boolean): Observer | void {
  // 3. value 需要处理的数据,asRootData 作为根数据

  // 4. 如果不是对象,如果是虚拟DOM。直接 return。 (这里需要注意,虚拟dom没有做响应式节约性能)
  if (!isObject(value) || value instanceof VNode) {
    return
  }

  // 5. 定义一个变量 ob ,类型是Observer或者void
  let ob: Observer | void

  // 6. hasOwn:value自身的属性上是否有 '__ob__' , 而且 value.__ob__是 Observer的实例
  // 简单来说:已经是响应式的,直接复用。
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    // 7. 需要监听;不是服务端渲染;是数组 或者 普通对象 ;可扩展;_isVue为false
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 8. new Observer(value),传入的值为 我们的data
    ob = new Observer(value)
  }

  // 如果是asRootData,添加计数vmCount
  if (asRootData && ob) {
    ob.vmCount++
  }

  // 10.返回 ob
  return ob
}

整个代码看下来:

  1. 判断传入的值不是对象,是虚拟 dom, 直接 return;
  2. 判断对象上是否已经有 __ob__属性,而且 __ob__ 上存储的是 Observer 实例,直接复用;
  3. 满足一些逻辑校验,开始new Observer()

总结一下,observe 主要是做逻辑过滤,然后开始 new Observer()

2. Observer

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */

/**
 * 1.
 * 每个被观察对象附加的观察者类
 * 对象。一旦连接上,观察者将转换目标
 * 对象的属性键到getter/setter
 * 收集依赖关系并分发更新。
 */
export class Observer {
  // 2. class直接定义变量, 相当于 function Observer(){}  ;Observer.value;Observer.dep; Observer.vmCount;
  value: any
  dep: Dep
  vmCount: number // number of vms that have this object as root $data

  constructor(value: any) {
    // 3.存储 value
    this.value = value

    // 4.创建一个依赖收集者
    this.dep = new Dep()

    // 5. 计数 把这个数据当成根data对象的实例数量
    this.vmCount = 0

    // 6. 给 data 设置一个 __ob__ 属性,值为 Observer 实例
    // 这个地方可以知道为什么我们 Vue中的数据,打印出来会带有 '__ob__'
    def(value, '__ob__', this)

    // 7. 如果是数组 (数组最后再讲,先说对象)
    if (Array.isArray(value)) {
      // 7.1 可以使用对象的 __proto__ 属性
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        // 7.2 不可以使用对象的 __proto__ 属性
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 7.3执行 observeArray
      this.observeArray(value)
    } else {
      // 8. 其他情况,对象
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */

  /**
   * 9.
   * 遍历所有属性并将其转换为
   * getter setter。此方法只应在以下情况调用
   * 值类型为Object。
   */

  walk(obj: Object) {
    // 10. 调用对象的属性,循环执行 defineReactive(对象, 对象的属性名)
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  // 11.观察Array项
  observeArray(items: Array<any>) {
    // 12. 遍历数组的每一项,全部都observe一下。
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

Observer 是一个类,使用 new Observer的时候,会在 value 上添加属性 __ob__,存储 Observer 的实例.

需要注意下,new Observer 的时候,在__ob__中存储了 dep 属性。对应的源码this.dep = new Dep();

然后判断 value 是 对象还是数组,分别做不同的处理。

  1. 对象:this.walk => defineReactive;
  2. 数组:protoAugment/copyAugment => 遍历每一项observe();

数组的情况后续再说,本节先说说对象的情况

对象的情况主要是遍历对象的每一个属性,对每一个属性都执行一次 defineReactive(obj, keys[i])

defineReactive(对象, 对象的属性)

defineReactive

/**
 * Define a reactive property on an Object.
 */
// 1. 在对象上定义响应式属性
export function defineReactive(
  obj: Object, // 传入的 对象:
  key: string, // 对象的 属性;
  val: any, //对象的属性值,在没有 getset的时候,直接返回对应的值。
  customSetter?: ?Function, // 自定义 setter
  shallow?: boolean // 是否是 浅层的响应式
) {
  // 2. new Dep() , 定义个对象用来 收集依赖。
  const dep = new Dep()

  // 3. Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。  简单来说,拿到这个属性的配置。
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // 4. 如果配置存在,如果 configurable===false  (该属性不可修改),直接 return
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  // 5. 满足预定义的getter/setter
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    // 6. 如果:没有getter或者有setter;而且传入的参数长度为2。设置第三个参数 val为
    val = obj[key]
  }

  // 7. 不是浅层的,监听 val;  childOb是布尔值
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true, // 可以枚举
    configurable: true, // 可以配置

    // 8.定义 get
    get: function reactiveGetter() {
      // 8.1 拿到真实的值
      const value = getter ? getter.call(obj) : val

      // 8.2 收集依赖
      if (Dep.target) {
        dep.depend()

        // 8.3 子对象
        if (childOb) {
          childOb.dep.depend()

          // 数组处理
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },

    // 9.定义 set
    set: function reactiveSetter(newVal) {
      // 9.1 真实的值
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */

      // 9.2 `newVal === value` 值没有改变的时候不触发后续逻辑;   (newVal !== newVal && value !== value) 这是什么意思? 防止 NaN == NaN (false) https://github.com/vuejs/vue/issues/4236
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */

      // 9.3 自定义 Setter
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      // 9.4 对于没有setter的访问器属性
      if (getter && !setter) return

      // 9.5 有 setter 走 setter
      if (setter) {
        setter.call(obj, newVal)
      } else {
        // 9.6 没有则直接赋值给 val
        val = newVal
      }

      // 10. 处理子元素
      childOb = !shallow && observe(newVal)

      // 11. 通知订阅者。
      dep.notify()
    },
  })
}

讲下defineReactive的主干逻辑。 利用Object.defineProperty(),给对象的属性设置了 get set,实现数据劫持;

在闭包中,定义了一个 对象 dep,收集和通知依赖。

原本我们获取或设置一个对象的属性,触发的都是默认的逻辑(获取就获取了,没有添加自定义的逻辑)。通过Object.defineProperty(),给对象配置 get set 可以在获取或设置对象属性的时候,自定义一些操作。

  • Object.defineProperty()相关内容可以参考 MDN。
  • 说实话,我一直在想 加 get set 有什么用? 还不是一样,获取数据,设置数据?
  • 到后来我才明白,在 get set 中除了原本的获取数据,设置数据,我们还可以加入我们自己想要定义的逻辑。
  • 例如上述代码的 dep.depend(),dep.notify();

收集和通知依赖

谁来收集? dep; 收集了谁? watcher; 先暂时有一个印象,后续单独研究;

当然 有些对象不仅仅只有一个层级,对于多个层级的对象,利用let childOb = !shallow && observe(val) 做了处理。

小tips

observe 自身有校验,不是对象的不会继续处理。 这种递归的思路,就能实现对象的所有层级都会被处理。

思考

  1. 我们传入的 data 为什么会多一个 __ob__ 属性?
  • 在处理我们传入的配置项 data 时,会 observe(data),随即会 new Observer
  • Observerconstructor 中 会执行def(value, '__ob__', this),也就是会把 Observer实例 绑定到 data__ob__ 上。
  • 这个地方存储 Observer实例 方便后续使用,后续会提到
  1. Observer 处理 data 会区分数组和对象?
  • 番茄其实很早就听说过,Vue.js 处理数据成响应式的,是区分对象和数组的。
  • 这里查看源码就得到了验证。
  • 为什么要区分对象和数组,为什么要分开处理,下一节看完数组的处理方式在细说。
  1. def 方法
  • def 方法是一个后续经常会见到的方法,这里详细说明一下
/**
 * Define a property.
 * 定义一个属性
 */
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable, // 是否可枚举
    writable: true, // 可写
    configurable: true, // 可改变可删除
  })
}

总结

  • 本节主要是阅读了 Vue.js 针对对象的响应式处理的方式。
  • 了解到 Vue2 响应式原理的底层 Object.defineProperty()