源码分析: 深入Vue响应式原理 响应式对象(1)

167 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

响应式对象

大家应该都知道响应式原理的核心API是Object.defineProperty。如果不了解请点击: Object.defineProperty 去MDN了解下这个API的用途。

那这个API是如何做到响应式对象的呢。

接下来我们从源码的角度来进行分析: 我们知道在组件的data中定义的数据,最终都能响应式,看下Vue2.x的源码是如何初始化data的:

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

function initData (vm: Component) {
  let data = vm.$options.data
  // 1.获取到data中定义的对象
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://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
  // 2.验证是否与定义的methods与props冲突;
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
    // 3.proxy代理;
      proxy(vm, `_data`, key)
    }
  }
  // observe data 4.oberve实现data对象响应式;
  observe(data, true /* asRootData */)
}

这里面主要分为如下几步:

  1. 获取到data中的返回对象;
  2. 验证是否与定义的methods与props冲突;
  3. proxy代理;
  4. oberve实现data对象响应式; 这里的第一步和第二部很容易理解,第四步是实现响应式对象的核心,第三步简单的分析下是为什么,贴上代码:
proxy(vm, `_data`, key)

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)
}

可以看到,这个地方其实也是利用了Object.defineProperty这个api,定义了存储描述符,让我们在vue内部data属性中定义的key通过代理的方式,可以使用this.key的方式去访问到。简化了我们的代码量。

当我们访问this.key的时候,其实内部返回的就是this._data.key的值。

在vue中我们也可以通过this._data.key的方式访问到我们定义的data,但不建议这么做,_data的意思就是内部属性data。

observe的逻辑我们下一节再来分析,未完待续深入响应式原理(2)