Vue源码解析之数据响应系统的使用(5)

isPlainObject是判断是否是一个纯对象的,如果data不是一个对象,在非生产环境下给出警告信息。

继续往下看:

// proxy data on instance
// 获取data对象的键
const keys = Object.keys(data)
// 获取props,是个对象
const props = vm.$options.props
// 获取methods,是个对象
const methods = vm.$options.methods
let i = keys.length
// 循环遍历data的键
while (i--) {
 const key = keys[i]
 // 如果methods存在,并且methods中存在与data对象相同的键,发出警告。data优先
 if (process.env.NODE_ENV !== 'production') {
  if (methods && hasOwn(methods, key)) {
  warn(
   `Method "${key}" has already been defined as a data property.`,
   vm
  )
  }
 }
 // 如果props存在,并且props中存在与data对象相同的键,发出警告。 props优先
 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)) { // isReserved 函数用来检测一个字符串是否以 $ 或者 _ 开头,主要用来判断一个字段的键名是否是保留的
  proxy(vm, `_data`, key)
 }
}
// observe data
observe(data, true /* asRootData */)

while中的两个if条件判断了props和methods中是否有和data对象相同的键,因为这三者中的属性都可以通过实例对象代理访问,如果相同就会出现冲突了。

const vm = new Vue({
 props: { a: { default: 2 } }
 data: { a: 1 },
 methods: {
  a () {
   console.log(3)
  }
 }
})

当调用vm.a的时候,就会产生覆盖现象。为了防止这种情况出现,就在这里做了判断。

再看else if中的内容,当!isReserved(key)成立时,执行proxy(vm,_data, key)。 isReserved函数的作用是判断一个字符串是否以 $ 或者 _ 开头, 因为Vue内部的变量是以$或_开头,防止冲突。如果 key 不是以 $或 _ 开头,那么将执行 proxy 函数

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

proxy函数通过Object.defineProperty在实例对象vm上定义了与data数据字段相同的访问器属性,代理的值是vm._data上对应的属性值。当访问this.a时,实际访问的是this._data.a的值。

最后一句代码是

// observe data
observe(data, true /* asRootData */)

调用observe将data数据对象转换成响应式的。