4
Vue版本:2.5.17-beta.0
new Vue({
    el: '#app',
    data() {
        return {
            message: 'Hello Vue!'
        }
    },
    mounted() {
        console.log(this.message)
    }
})

上面是一个简单的 Vue 示例,当运行 mounted 生命周期时,控制台会输出 Hello Vue!
那么为什么通过 this.message 可以访问到 data 中定义的数据,下面让我们一起分析一下。

new Vue(options) 时会调用 Vue 类中的 this._init() 原型方法,该原型方法中又会调用 initState(vm) 进行 props、methods、data 等初始化(上述内容在《Vue源码笔记 — 数据驱动--new Vue 发生了什么》文章中记录过),initState 方法在 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(vm) 是我们这次的主线逻辑,可以看到首先判断 opts.data 是否定义,如果定义就会调用 initData 并传入当前实例对象(其中 opts.data 就是 vm.$options.data,而 $options 是在 _init() 中通过 new Vue(options) 传入的选项 optionsVue.options 合并而成, 所以 opts.data 其实就是获取 new Vue 传入的 options.data 选项)。initData 源码如下:

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? 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)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 判断 data中的key 是否和 methods、props中的key 是否有重复 有就报警告
  // 因为 data、methods、props中的key 都挂载到 vm下
  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 */)
}

上述源码中我们可以看到大概的逻辑是,首先会获取 data 数据并挂载到 vm._data 中,然后获取 datakeys 数组循环并判断当前 data.keymethods.keyporps.key 是否有重复,如果有会报警,没有则会调用 proxy(vm, '_data', 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)
}

此时,调用方传入的 targetvm 实例对象,sourceKey'_data' 字符串,而 key 则为 data.key,最后通过 Object.defineProperty 语法去监听 vm 实例对象对应的 key 并设置 getter

由此我们可以知道,当访问 this.message 能获取到数据,是因为 getter 返回的 this._data.message,而 this._data 在上述的 initData 函数源码中被赋值为 data 数据。

最后,propsmethods 其实都是类似这种现实,都会挂载到当前的实例根下,可以直接通过 this.xxx 获取到,这也是为什么在 initData 函数源码中需要判断 data.keyprops.keymethods.key 是否有重复的原因。


Dragon
40 声望4 粉丝