Skip to content

data 响应式

javascript
// 初始化 data
function initData (vm: Component) {
  let data = vm.$options.data
  // 如果 data 是函数,先执行获取返回值。
  // 因为 data 会被劫持实现响应式,假如 set 中直接 data[key] = newVal
  // 当我们修改 data 属性时会先调用 get,然后调用 set,set 内是 data[key] = newVal
  // 又会调用 get,然后调用 set,不断重复这样调用,造成栈内存溢出。
  // 因此 data 会将地址值拷贝一份到 vm._data 上,当我们修改 data 属性时,
  // set 内是 vm._data[key] = newVal,不会一直重复调用。
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // 保证 data 是纯对象
  if (!isPlainObject(data)) {
    data = {}
  }
  
  // 将 vm._data 代理到 vm 上,方便取数据
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    // method 不能和 data 属性重名
    if (methods && hasOwn(methods, key)) {
      // ...
    }
    // props 不能和 data 属性重名
    if (props && hasOwn(props, key)) {
      // ...
    } else if (!isReserved(key)) { // 如果属性名以 $ 或 _ 开头,不会代理到 vm 上。
      proxy(vm, `_data`, key) // 代理 vm._data 到 vm 上
    }
  }
  // 实现 data 响应式
  observe(data, true /* asRootData */)
}

function getData (data: Function, vm: Component): any {
  return data.call(vm, vm)
}
javascript
// 通过 observe 和 Observer 类的互相调用实现 data 所有层次属性的响应式。
// observe 用来决定哪些值会 new Observer(value),实现响应式,并返回实例。首先会判断
// !isObject(value) || value instanceof VNode
// 如果满足直接 return,不处理。接着判断
// hasOwn(value, '__ob__') && value.__ob__ instanceof Observer
// 如果满足说明这个 value 已经被处理过了,直接返回 ob。
// 最后判断 Array.isArray(value) || isPlainObject(value)
// 如果满足,会对数组或纯对象 new Observer()

// Observer 类中会对进入这里的 value 定义属性 __ob__,值就是 Observer 实例,
// 表示这个 value 已经被处理过了。通过 Object.defineProperty 定义并设置不可枚举,
// 如果 Observer 构造器中 value.__ob__ = this,defineReactive 中 observe(val)
// 会让这个空对象再次进入 Observer 构造器,又给这个空对象添加了 __ob__,导致死递归。
// 如果传入构造器的 value 是纯对象,Observer 类会遍历对象的每个属性并调用 
// defineReactive 实现响应式。
// defineReactive 内通过 Object.defineProperty 定义存取描述符,在对象的取值
// 和赋值时都能做一些事,实现响应式。目前 get 就是返回 value,set 就是 val = newVal
// 并 observe(newVal),因为 newVal 可能是数组或纯对象,需要变成响应式。
// 如果传入构造器的 value 是数组, 因为数组的响应式并不通过 defineReactive 实现,
// Observer 类会遍历数组的每个元素并调用 observe,observe 中又只有数组或纯对象
// 才会 new Observer(value),这样不断重复调用,嵌套的数组的中只会有纯对象会被遍历属性
// 并调用defineReactive 实现响应式,这样就实现了 data 中每个对象的所有层次属性的响
// 应式。

function observe (value: any, asRootData: ?boolean): Observer | void {
  // 如果不是对象或者是虚拟DOM,不进行劫持。
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 如果这个值已经被劫持过了,不进行劫持。
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { 
    ob = value.__ob__
  } else if (
    (Array.isArray(value) || isPlainObject(value))
  ) {
    // 进行劫持
    ob = new Observer(value)
  }
  return ob
}

// 内部调用 defineReactive 实现响应式
class Observer {
  value: any;
  dep: Dep;

  constructor (value: any) {
    this.value = value
    // 通过 Object.defineProperty 往 value 上定义 __ob__ 属性, 表示被劫持了
    // 值是 Observer 实例
    def(value, '__ob__', this) 
    if (Array.isArray(value)) {
      // 是数组
      this.observeArray(value)
    } else {
      // 是纯对象
      this.walk(value)
    }
  }

  // 遍历对象的每个属性并对它们调用 defineReactive 实现响应式。
  walk (obj: Object) {
    const keys = Object.keys(obj)
    // 每一个属性进行劫持
    for (let i = 0; i < keys.length; i++) { 
      defineReactive(obj, keys[i])
    }
  }

  // 遍历数组每个属性并对它们调用 observe 实现数组内对象的响应式。
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      // 调用 observe,如果是数组或纯对象,observe 内会 new Observer
      // 数组的话又会回调用 observeArray,最终只有对象会实现响应式。
      observe(items[i])
    }
  }
}

function defineReactive (
  obj: Object,
  key: string,
  val: any
) {
  observe(val)
  // 为该属性实现响应式
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      return value
    },
    set: function reactiveSetter (newVal) {
      if (newVal === value) {
        return
      }
      val = newVal
      // newVal 可能是数组或纯对象,需要变成响应式。
      observe(newVal)
    }
  })
}