vue源码分析(七)

885 阅读3分钟

一、响应式对象

vue是通过bject.defineProperty进行数据劫持,把数据变成响应式的,这也是vue的一个核心思想

在执行_init的时候会执行initState函数,initState中,会通过initPropsinitData来初始化props和data

export function initState (vm: Component) {
  // src/core/instance/state.js
  ...
  if (opts.props) initProps(vm, opts.props)
  if (opts.data) {
    initData(vm)
  }
  ...
}

initData函数会调用observe(data, true /* asRootData */)函数,observe函数首先会判断他是否是一个对象,如果不是一个对象,或者它是一个vnode,那么就不会接着执行。之后会判断传入的data是否有通过Observer类构造出的__ob__属性,如果有的话,则说明他已经是一个响应式的对象,则ob直接使用之前的data.__ob__,如果不满足,那么会判断shouldObserveshouldObserve定义在当前文件,默认为true,通过toggleObserving方法可以进行值的修改,在initProps的时候,会判断props是否是一个根部的props,如果是(则不需要变为响应式),会通过toggleObserving先变为false,执行到最后再变为true。接着会判断他是否是一个数组,或对象,并且会通过Object.isExtensible判断他是否是一个可扩展的对象,所以如果想阻止data中的一些恒量变为响应式的,通过Object.freeze即可。最后会调用new Observer(value)

// 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.
 */
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() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

在执行new Observer的时候,会调用def(value, '__ob__', this)def定义在 src/core/util/lang.js下,他通过Object.defineProperty来控制这个对象属性的enumerable是true,还是false(是否可枚举)。当前的__ob__是不可被枚举的。然后会判断他是否是一个数组,如果不是一个数组,会调用walk方法,如果是数组,会遍历数组去调用observe方法,会再次new Observer这样会让数组中的对象最终都会调用walk函数。walk函数会遍历对象,然后调用defineReactive函数,这样就可以解释为什么会对__ob__做不可枚举的处理,因为__ob__使用者并不会手动去修改,也就不需要是一个响应式的属性。

// src/core/observer/index.js
/**
 * 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.
 */
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

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

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

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

defineReactive函数首先他会尝试拿到他原始的configurable,enumerable,value,writable,然后会去获取他的getset,如果没有getter或者有setter(该属性是一个对象)并且该函数传入了两个参数,那么会去执行observe这样就保证了对象中的属性值是一个对象,也会变成响应式的。最后通过Object.defineProperty给该属性绑定getter(依赖收集)和setter(派发更新),而initProps最终也会调用defineReactive把props变为响应式的


/**
 * Define a reactive property on an Object.
 */
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,
    get: function reactiveGetter () {
      ...
    },
    set: function reactiveSetter (newVal) {
      ...
    }
  })
}