Vue.js源码学习——Vue对象在data中定义的属性为什么可以直接通过this.xxx访问?

Posted 小馒头儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue.js源码学习——Vue对象在data中定义的属性为什么可以直接通过this.xxx访问?相关的知识,希望对你有一定的参考价值。

我们在Vue对象中可以通过this.xxx去访问Vue中data对象中定义的数据,但是为什么可以直接使用this.xxx来访问呢?我们可以用过看源码的方式来进行理解。

当我们使用new的方式创建一个Vue实例的时候,实际上调用的是一个Vue的方法。

/* src/core/instance/index.js */
function Vue (options) {
  if (process.env.NODE_ENV !== \'production\' &&
    !(this instanceof Vue)
  ) {
    warn(\'Vue is a constructor and should be called with the `new` keyword\')
  }
  this._init(options)
} 

初始化的时候,Vue方法中主要调用了_init这个方法。

这个_init方法实际上是一个挂在在Vue原型上的一个方法。同样在这个文件中含有一行代码initMixin(Vue)就是完成了挂载_init方法在Vue原型上的一个操作。

然后我们进入_init(options)来看进行了哪些操作。这个时候进入src/core/instance/init.js找到initMixin方法。

initMixin一开始就可以看到Vue.prototype._init = function() {} 这个就是Vue对象原型上的_init方法了。然后来看这个方法中进行了哪些操作?这个时候忽略一些细节,直接看到有一段全是initXXX的函数调用。这一块大致就是Vue对象初始化的一些流程,比如生命周期,事件等等。

/* src/core/instance/init.js */
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    /* more details */
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, \'beforeCreate\')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, \'created\')
    /* more details */
    }
} 

关于data的处理就聚焦在这个initState方法上。initState这个方法是引入到这个文件的,所以我们找到initState这个方法真正定义的地方src/core/instance/state.js。

initState定义的结构看起来很简单,就是根据我们的options上的不同属性来做相应的初始化处理。

/* src/core/instance/state.js */
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)
  }
} 

我们的主要目标还是data,这个时候我们我们就主要去看data属性下进行的操作。data选项存在时候就调用了initData,不存在的时候就调用了observer,同时把data用一个空对象赋值。observer和数据响应式相关,这里先不详细说明。

接下来就是去看initData做了什么处理了?

/* src/core/instance/state.js */
function initData (vm: Component) {
  let data = vm.$options.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
  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)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
} 

initData的操作分为三个部分:

  • 判断data是否是一个函数
  • 数据代理
  • 响应式(暂不详细说明)
  1. 我们在创建一个新的Vue对象的时候,data是一个函数,返回一个含有定义参数的新对象,如果不是一个函数,就会进行一个报错。具体原因和组件相关。

    new Vue({
      data() {
       return {
          message: \'Hello world\',
       }
        },
    });
    
    new Vue({
      data: {
        message: \'Hello world\',
        },
    }); 
  2. 数据代理的时候要确保data中的属性名称不能和propsmethod和保留属性不能重名,因为最后这些都会挂载在Vue的实例上。代理的实现部分依赖proxy这个方法。

    /* src/core/instance/state.js */
    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)
    } 

    通过设置一个对象上的访问器属性的getset,就能使我们在访问vm.xxx的时候去访问vm._data.xxx了。vm_data属性则是在initData中判断data是否使一个函数的时候的赋值操作。这样当我们去访问vm.xxx实质上就是访问的我们定义的data上的属性了。

以上是关于Vue.js源码学习——Vue对象在data中定义的属性为什么可以直接通过this.xxx访问?的主要内容,如果未能解决你的问题,请参考以下文章

Vue.js 源码学习笔记 -- 分析前准备 待续

vue.js学习日记-组件篇

Vue.js入门学习

工作+学习总结

译Vue源码学习:Vue对象构造函数

从Vue.js源码角度再看数据绑定