浅曦Vue源码-8-响应式数据-initState(2)-数据观察observe

384 阅读6分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。

一、前情回顾 & 背景

上一篇我们讨论了 initState() 方法调用时 initProp 具体做了哪些事情,initProps 的主要工作就是 for in 遍历 propsOptions,把 prop 通过 defineReactive 都变成响应式的,最后把这些响应式的数据放到 vm._props 上,最后还要把这些 prop 代理到 vm 上。

在上面的这些工作中,上一篇中通过 defineReactive 变成响应式的数据,其中有一个很重要的步骤就是通过 observeprop 的子级属性甚至孙子级属性都变成响应式的,这部分可以单独拎出来讲一讲,所以就有了这一篇小作文。

二、observe 的时机

在开始 observe 的源码之前,有个小细节也需要提一下,那就是 observe 方法在 initProps 过程中被调用的时机。在 initProps 过程中一共有两处调用了 observe 方法:

1. validatePorp

initPropsfor in 循环时获取 prop 的默认值时,即调用德 validatePorp 中;

export function validateProp (...): any {
  // ....
  if (value === undefined) {
    value = getPropDefaultValue(vm, prop, key)
    // ....
    observe(value)
  }

  return value
}

2.2 在 defineReactive 方法中

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // ....
  let childOb = !shallow && observe(val)
  // ....
}

第一个在 validateProp 方法中调用 observe 是因为当默认值是对象或者数组时,它是调用工厂函数得到一个新的数据,尚未处理成响应式数据,这里调用 observe 就是将这个默认值处理成响应式。

第二处就是在 defineReactive 中,这里相当于是第一步的延续,这两步并不冲突,第一处有一种没处理,就是当 prop 的值不是默认值,而是从 propsData 中获取的值,这个时候 validate 不处理,这个值已经是一个响应式数据,或者就是没处理过的,此时就需要 defineReactive 中的第二处来处理了。

当然,observe 中也有防止重复处理的逻辑,只要处理过了,就会立即返回之前的处理结果,这个后面源码中会详细的介绍这一过程。

三、observe 方法

3.1 方法位置:

src/core/observer/index.js -> observe

3.2 方法作用:

为参数 value 尝试创建 Observer 类的一个实例,即观察者,如果成功创或者 value 之前就已经被创建过了,建就返回实例,具体分以下几步:

  1. 如果接收对的 value 参数不是对象或者是个 VNode 实例则不处理
  2. 判重处理,如果 value 对象上存在 __ob__ 属性,则说明这个对象已经被处理成响应式了,直接返回响应式结果
  3. 创建观察者实例并返回

至于观察者类和观察者实例,我们下个主题将会细致讲解。

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 如果不是对象或是 VNode 实例,则不处理,直接退出
  if (!isObject(value) || value instanceof VNode) {
    return
  }

  let ob: Observer | void

  // 判重,如果 value 对象存在 __ob__ 属性,标识已经给 vlaue 创建过了,
  // 直接返回 __ob__ 属性
  // 从这里能看出,为一个 value 创建观察者对象成功就会给 value 添加 __ob__ 属性,
  // 值就是观察者实例本身
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 创建观察者实例
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

四、defineReactive

4.1 方法位置:

src/core/observer/index.js -> defineReactive

4.2 方法作用:

在一个对象上定义一个响应式的属性,其核心就是大家熟知的 Obeject.definePorperty 方法,具体实现通过以下步骤:

  1. 实例化一个 Dep,可以看得出来,前面是个 for in 遍历 propsOptions,将每个 key 都设置为响应式,所以每个 prop 都有一个 Dep
  2. 通过 Object.getOwnPropertyDescriptor(obj, key) ,获取这个 prop 对应原来的属性表述对象,所谓属性描述对象就是 Object.defineProperty 的第三个参数,即定义属性行为的对象,如果描述对象的 configurablefalsereturn 退出,因为这个对象不可配置;
  3. 从原有的属性描述对象获取 key 对应的 gettersetter
  4. 如果这个属性的值还是对象,则调用 observe(val),得到 childObj 用于后面依赖收集,这里分两种情况:
    • 4.1 如果 val 已经在前面处理默认值的时候被处理成响应四数据了,则返回 val.__ob__ 属性,这个其值是个 __ob__Observer 的实例,用来实现依赖收集和数据更新时派发更新信号;
    • 4.2 如果 val 尚未被处理,则递归处理 val 的属性及其子属性,最后返回 Observer 实例;
  5. 通过 Object.defineProperty 设置响应式;

总结起来,响应式的核心就是拦截 obj[key] 的读取和设置,当读取这个值的时候,就会触发 getter,收集依赖,就是看看是哪个 watcher 里用到这个 obj[key] 的值,通过 dep 记录下来;然后当你设置的时候,就会触发 setter,然后就去让 dep 去通知那些 watcher 开工,重新获取这个对象的值,这个时候就可以拿到更新后的值了。

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 实例化 dep,一个 key 一个 dep,当然这里是 initProp 调用的,key 是 propOptions 中的属性
  // 如果 prop 对应的值是个也是对象,则后面 observe 的时候也会为子对象的 key 创建的
  const dep = new Dep()

  // 获取 obj[key] 的原有的属性描述对象,若不可配置的话直接 return
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // 保存属性描述对象中的 getter 和 setter,getter用于 获取 val 值
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    // 如果没有定义 setter 或者 getter 并且只传了两个参数,val 就直接赋值为 obj[key]
    val = obj[key]
  }

  // 借助 observe 递归处理子属性甚至更深层的嵌套属性
  let childOb = !shallow && observe(val)

  // 响应式核心
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // get 拦截对 obj[key] 的读取操作
    get: function reactiveGetter () {
      // 调用 getter 获取 value 省略...
      if (Dep.target) {
        // depend 方法依赖收集,在 dep 中添加 watcher,也在 watcher 中添加 dep
        dep.depend()

        // childObj 表示对象中子对象的观察者对象,如果存在,也要对其进行依赖收集
        if (childOb) {
          // 这就是 prop.key.childKey 被更新时也能有响应式的原因
          childOb.dep.depend()
        }
      }
      return value
    },

    // set 拦截对 obj[key] 的设置操作
    set: function reactiveSetter (newVal) {
      // ....
      // observe 新值,将新值变为响应式数据
      childOb = !shallow && observe(newVal)
      dep.notify() // 派发更新
    }
  })
}

五、总结

本文详细讨论了 initProps 中的两个重要方法:observedefineReactive

其中 observe(val),当 val 是对象或者数组时,会创建 Observer 实例,而 Observer 的核心则是给 val 扩展 __ob__ 属性,然后递归的调用 defineReactive,把没给属性都变成响应式;

defineReactive 核心是 Object.defineProperty 拦截 val 的属性读取和设置,读取时收集依赖,设置时派发更新。

到这里我们完成了 initStateinitPropsobservedefineProperty,写了这么多居然才到这里,别说读的人很崩溃,我这个写的人也很崩溃。。。现在 initProps 的坑也没填完,因为还有两个重要的东东:ObserverDep 这两部分将会再下个主题讨论;

不过有一说一,坚持一下吧,我也是第一次尝试写这么长的系列更文,之前都是写教程,这也是在挑战我自己,希望屏幕前的看官老爷也挑战一下自己,加油!!!