vue2响应式原理(1)--初始化响应式对象data

190 阅读4分钟

以vue2.7为学习版本

initMixin

src/core/instance/init.ts中,有一个initMixin方法,这是vue初始化的开始

export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    const vm: Component = this
    // ...
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate', undefined, false /* setContext */)
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
}

在这个方法里面,在调用beforeCreate和created生命周期函数之间,初始化了inject、provide、initState,下面看一下initeState做了一些什么

initState

src/core/instance/state.ts

export function initState(vm: Component) {
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    const ob = observe((vm._data = {}))
    ob && ob.vmCount++
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

可以看出,初始化顺序是 1.props -> 2.methods -> 3.data -> 4. computed -> 5.watch,所以平时写代码的时候,可以在data中通过this拿到props中属性、methods中的方法,

所以我们也可以在created生命周期中拿到props、methods、data、computed、watch,在beforeCreate中不可以,下面主要分析一下initData

初始化data

src/core/instance/state.ts

function initData(vm: Component) {
  let data: any = vm.$options.data
  data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
  if (!isPlainObject(data)) {
    data = {}
    __DEV__ &&
      warn(
        'data functions should return an object:\n' +
          'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      )
  }
  // proxy data on instance
  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]
    if (__DEV__) {
      if (methods && hasOwn(methods, key)) {
        warn(`Method "${key}" has already been defined as a data property.`, vm)
      }
    }
    if (props && hasOwn(props, key)) {
      __DEV__ &&
        warn(
          `The data property "${key}" is already declared as a prop. ` +
            `Use prop default value instead.`,
          vm
        )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  const ob = observe(data)
  ob && ob.vmCount++
}

export function getData(data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e: any) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function proxy(target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter() {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter(val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

首先拿到data判断是不是函数,如果是函数,调用getData,执行并返回 data.call(vm,vm),绑定data这个函数的this为当前组件实例,才能在data中通过this拿到props或者methods;

通过while循环,判断data中定义的属性名是否和props和methods选项中的属性名重复

调用observe(data)将data变成响应式

proxy(vm, _data, key)通过Object.defineProperty把data中的属性代理到vm实例上,因为vm._data = isFunction(data) ? getData(data, vm) : data || {},data选项中的属性赋值给了_data,我们能通过this.xxx的方式拿到this._data.xxx上的属性

observe 判断是否要监测

src/core/observer/index.ts

/**
 * 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,
  shallow?: boolean,
  ssrMockReactivity?: boolean
): Observer | void {
  if (!isObject(value) || isRef(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    (ssrMockReactivity || !isServerRendering()) &&
    (isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value.__v_skip /* ReactiveFlags.SKIP */
  ) {
    ob = new Observer(value, shallow, ssrMockReactivity)
  }
  return ob
}

首先参数value必须是对象,不能是ref,也不能是Vnode实例,否则直接返回;

if判断中,如果value已经被监测了,就直接返回value.ob,被监测了的数据会有一个_ob_属性;

else if条件中Object.isExtensible(value),说明对象是不能被冻结的,不然vue不会追踪变化,也就是说,当你页面中有些对象只是展示作用,可以使用Object.freeze()来阻止vue将它变成响应式对象,提升页面性能;

Observer 对象和数组的监测方式不一样

src/core/observer/index.ts

/**
 * 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 {
  dep: Dep
  vmCount: number // number of vms that have this object as root $data

  constructor(public value: any, public shallow = false, public mock = false) {
    // this.value = value
    this.dep = mock ? mockDep : new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (isArray(value)) {
     // 数组情况后面再看
    } else {
      /**
       * Walk through all properties and convert them into
       * getter/setters. This method should only be called when
       * value type is Object.
       */
      const keys = Object.keys(value)
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock)
      }
    }
  }
}

export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

首先,通过def函数把自身实例添加到监测对象value的_ob_属性上面,代表该对象已经被监测了,所以上面observer函数中这段代码, if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ }说明已经被监测的数据不需要再次监测了;

然后就是遍历对象value的属性,使用defineReactive将其变成响应式 ,_ob_属性将不会被遍历,因为不可枚举,也用不着遍历,所以用def方法,而不是直接给value添加

defineReactive 添加get和set

src/core/observer/index.ts

/**
 * Define a reactive property on an Object.
 */
export function defineReactive(
  obj: object,
  key: string,
  val?: any,
  customSetter?: Function | null,
  shallow?: boolean,
  mock?: 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) &&
    (val === NO_INIITIAL_VALUE || arguments.length === 2)
  ) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val, false, mock)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // 收集依赖
    },
    set: function reactiveSetter(newVal) {
     //  触发更新
    }
  })
  return dep
}

首先,通过Object.getOwnPropertyDescriptor拿到属性描述符,configurable为false那就直接返回;

拿到要观测的属性的get和set,当getter不存在或者setter存在,并且val初始值为{}或者参数为2,就执行val=obj[key],给val赋值;

childOb如果不是shallow浅响应,那么就递归observe(val, false, mock),将每个对象属性都变成响应式,如果val不是对象,if (!isObject(value) || isRef(value) || value instanceof VNode) { return }那observe返回undefined;

然后接下来就是访问这个属性的时候就会在get里面收集依赖,修改属性的时候就会在set中触发依赖更新