vue源码分析 - 为什么通过this可以访问到data中的数据

讲data之前,先来看下在new Vue() 时,发生了什么?

当通过 import Vue from ‘vue’方式来引入vue,此时在new 之前 ,Vue的原型上已经挂载了许多初始化的属性及方法,通过

initMixin(Vue) 
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

当我们new Vue时,会执行以下Vue函数,里边_init()方法,是通过initMixin函数定义的,继续往下看:

( 以下所有代码在开头都会有一个路径 )

//  src -> core -> instance -> index.js 
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

// 初始化Vue
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原型上定义的
}
// 在import Vue from ‘vue’ 时,会执行以下函数
initMixin(Vue) // 函数执行时,定义了Vue原型上会定义_init 方法
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

initMixin函数:

在initMixin函数中,Vue原型上定义了_init 方法,其中的多数代码都去掉了,merge options注释 ,这个是在初始化时会把el / data / props / methods 上边的所有属性都合并到 实例的 $options 上去 ( vm.$options ),如何合并的会在以后文章中讲到。

继续往下,又会看到一系列初始化的函数, 本篇文章主要介绍下 initState(vm)中的 initData(vm)

//  src -> core -> instance -> init.js 
let uid = 0
export function initMixin (Vue: Class) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    ··· ···
    // a flag to avoid this being observed
    ··· ···
    // merge options
    ··· ···
    // expose real self
    vm._self = vm
    initLifecycle(vm) // 初始化钩子函数
    initEvents(vm) // 事件
    initRender(vm) // render函数
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm) // 为data中的数据添加getter setter,及数据的响应式/监听
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    ··· ···
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

initState函数:

在initState函数中,分别初始化了props / methods / data , 以及监听data中的数据, 我们来看下 initData( vm )函数:

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

initData函数:

真正的data初始化是这个函数,首先会从实例的$options中获取到data,判断data是函数还是对象,因为data可以是一个函数(组件),也可以是一个对象(根实例),如果是函数会执行getData方法,getData方法简单来说就是返回了data函数执行的结果(是一个对象)赋值给 data和 vm._data;如果是对象直接把data赋值给data和 vm._data;如果data不存在,把空对象赋值给 data和 vm._data(vm._data 就是data中的数据对象) 

继续,isPlainObject(data)这个函数判断条件是:data是一个function,但是返回的结果不是一个对象,会抛出警告!

接下来data中的key会组成一个数组,遍历每个keymethods / props 比对,如果一样,会抛出异常,也就是说:data对象中定义的key(键)不能与props 或 methods 定义的key(键)一样!

继续,isReserved(key)函数判断key(键) 不可以 以_ 或 $ 符号开头,在文档中给出了原因:

 继续,proxy(vm, `_data`, key) 这个函数是关键,为data每个数据添加getter setter,同时也能看到为什么通过this(vue实例)可以访问到data中的数据

//  src -> core -> instance -> state.js 
// 初始化data
function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function' // vm._data 就是return 返回的对象
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) { // 判断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) // [key1, key2, key3, ··· ···]
  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)) { // 判断data中的key 是否与props和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) // 为data每个数据添加getter setter
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

proxy函数:

其实proxy函数很简单,通过 Object.defineProperty(target, key, sharedPropertyDefinition)会把data中的数据代理转发到vue实例上了。

当我们通过vm.xxx 访问data中的数据时,实际上访问的是: this[sourceKey][key] , 也就是 vm. _data.[key]  ,而vm._data 在上边提到过,它正是data中的数据对象,所以,当我们通过 实例 访问data中的数据时,访问的是 this._data.xxx

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
// proxy(vm, `_props`, key)
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)
}

 

你可能感兴趣的:(vue源码分析)