Vue源码学习3.1:响应式对象

854 阅读5分钟

建议PC端观看,移动端代码高亮错乱

Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法:

// src/core/instance/state.js
export function initState (vm: Component{
  // ...
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // ...
}

initState 方法主要是对 propsmethodsdatacomputedwathcer 等属性做了初始化操作。这里我们重点分析 propsdata,对于其它属性的初始化我们之后再详细分析。

看个流程图:

其中 props 部分之后的章节还会深入分析,这里只需知道 initProps 调用了 defineReactive 即可

1. initProps

// src/core/instance/state.js
function initProps (vm: Component, propsOptions: Object{
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  
  if (!isRoot) {
    // 关闭观测的开关,observe将是无效调用
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    
    const value = validateProp(key, propsOptions, propsData, vm)
    
    // 简化后的
    defineReactive(props, key, value)
    
    // 数据代理,对于非根实例的子组件而言,代理发生在 Vue.extend 阶段
    // 这是一种优化手段,不用为每个组件实例都调用 Object.defineProperty 来实现代理
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  // 打开观测的开关
  toggleObserving(true)
}

initProps 的初始化主要过程:

  • 关闭观测的开关,具体的之后章节还会介绍,这里先简单了解:
    • 其实就是将 src/core/observer/index.js 文件中的 shouldObserve 全局变量置为 false
    • 这使得 defineReactive 中调用 observe 是一个无效调用。
    • 因为对于对象的 prop 值,子组件的 prop 值始终指向父组件的 prop 值,只要父组件的 prop 值变化,就会触发子组件的重新渲染,所以这个 observe 过程是可以省略的。
  • 遍历定义的 props 配置。遍历的过程主要做两件事情:
    • 一个是调用 defineReactive 方法把每个 prop 对应的值变成响应式,可以通过 vm._props.xxx 访问到定义 props 中对应的属性。
    • 另一个是通过 proxy 把对 vm.xxx 的访问代理到 vm._props.xxx 上。这里的一个细节是对于非根实例的子组件而言,代理发生在 Vue.extend 阶段,在之后章节还会介绍。
  • 开启观测的开关。

2. initData

// src/core/instance/state.js
function initData (vm: Component{
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  
  // ...

  const keys = Object.keys(data)
  
  // ...
  
  let i = keys.length
  while (i--) {
    const key = keys[i]
    
    // 简化后的...
    proxy(vm, `_data`, key)
  }
  // observe data
  observe(data, true /* asRootData */)
}

initData 的初始化主要做两件事:

  • 遍历 data 对象,通过 proxyvm.xxx 代理到 vm._data.xxx
  • 调用 observe 方法观测整个 data 的变化,把 data 也变成响应式,可以通过 vm._data.xxx 访问到定义 data 中对应的属性。

可以看到,无论是 props 或是 data 的初始化都是把它们变成响应式对象,这个过程我们接触到几个函数,接下来我们来详细分析它们。

其中 proxy之前的章节已经介绍过了,这里就不介绍了。

3. observe

observe 的功能就是用来监测数据的变化,它的定义在 src/core/observer/index.js 中:

// src/core/observer/index.js

// 观测开关
export let shouldObserve: boolean = true
export function toggleObserving (value: boolean{
  shouldObserve = value
}

export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve && // 观测开关打开
    !isServerRendering() && // 非SSR
    (Array.isArray(value) || isPlainObject(value)) && // value是数组或普通对象
    Object.isExtensible(value) && // value可扩展
    !value._isVue // value是非vue对象
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

observe 方法的作用就是给非 VNode 的对象类型数据添加一个 Observer,如果已经添加过则直接返回,否则在满足一定条件下(见注释)去实例化一个 Observer 对象实例。接下来我们来看一下 Observer 的作用。

4. Observer

Observer 是一个类,它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新:

// src/core/observer/index.js
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number// 将当前对象作为根$data的vm实例的数量

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__'this)
    if (Array.isArray(value)) {
      // ...
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

Observer 的构造函数逻辑很简单,首先实例化 Dep 对象,这是给 Vue.set 用的,在之后的章节会介绍。

接着通过执行 def 函数把自身实例添加到数据对象 value__ob__ 属性上,def 的定义在 src/core/util/lang.js 中:

// src/core/util/lang.js
export function def (obj: Object, key: string, val: any, enumerable?: boolean{
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}
  • value 对象上多了一个 __ob__ 的属性,指向 Observer 实例。
  • 同时 enumerable 默认置为 false,这样当 for 循环时不会遍历到这个属性。

回到 Observer 的构造函数,接下来会对 value 做判断,对于数组会调用 observeArray 方法,否则对纯对象调用 walk 方法。可以看到 observeArray 是遍历数组再次调用 observe 方法,而 walk 方法是遍历对象的 key 调用 defineReactive 方法,那么我们来看一下这个方法是做什么的。

5. defineReactive

defineReactive 的功能就是定义一个响应式对象,给对象动态添加 gettersetter,它的定义在 src/core/observer/index.js 中:

// src/core/observer/index.js
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
{
  const dep = new Dep()

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

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    getfunction reactiveGetter ({
      // ...
      return value
    },
    setfunction reactiveSetter (newVal{
      // ...
    }
  })
}

defineReactive 函数最开始初始化 Dep 对象的实例,接着拿到 obj 的属性描述符,然后对子对象递归调用 observe 方法,这样就保证了无论 obj 的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改 obj 中一个嵌套较深的属性,也能触发 gettersetter。最后利用 Object.defineProperty 去给 obj 的属性 key 添加 gettersetter。而关于 gettersetter 的具体实现,我们会在之后介绍。

总结

如果我们有如下的对象

{
    a: 1,
    b: [2, 3, 4],
    c: {
        d: 5
    }
}

经过观测之后:

{
    __ob__,          // Observer类的实例,里面保存着Dep实例 __ob__.dep => dep(uid:0)
    a: 1,            // 在defineReactive闭包里存在dep(uid:1)
    b: [2, 3, 4],    // 在defineReactive闭包里存在着dep(uid:2),还有b.__ob__.dep => dep(uid:3)
    c: {             // 在defineReactive闭包里存在着dep(uid:4)
        __ob__,      // Observer类的实例,里面保存着Dep实例__ob__.dep => dep(uid:5)
        d: 5         // 在闭包里存在着dep(uid:6)
    }
}