vue源码解读系列(4):为什么通过this.xxx可以访问到data的数据

该页面 ctrl + F 搜索 下一个 来查看流程

我们看看 new Vue 的时候发生了什么,我们为什么可以通过 this.xxx 访问到 data 中的数据。

看源码要确立目标,想要看哪里的知识,就去看哪里,别的无关的函数就不要去看了,要不一个套一个,一会就给你看懵逼了

分析

1、vue的最初形态

// 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'

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)
}
// `this._init(options)` 这个函数在 `initMixin(Vue)` 里面定义了
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

2、分析 initMixin 函数,主要做了一些初始化的操作,挂载 dom 节点等

// src/core/instance/init.js

export function initMixin (Vue: Class<Component>) {
     
  Vue.prototype._init = function (options?: Object) {
     
    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')
	// 挂载dom节点
	if (vm.$options.el) {
     
      vm.$mount(vm.$options.el)
    }
  }
}

3、分析 state.js ,这个里面都是初始化状态,如props、methods、data、computed、watch

// 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 拷贝了一份给 vm._data

// src/core/instance/state.js

function initData (vm: Component) {
     
  // 拿到data里的数据
  let data = vm.$options.data
  // 判断data是不是个函数,并且拷贝了一份给vm._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
    )
  }
  // 拿到data
  const keys = Object.keys(data)
  // 拿到props
  const props = vm.$options.props
  // 拿到methods
  const methods = vm.$options.methods
  let i = keys.length
  // 判断如果methods或props命名和data重复报错,否则去代理proxy(vm, `_data`, key)
  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 */)
}

这里通过 Object.defineProperty_data 变为 getter 和 setter,我们访问 vm.key,也就是访问 this[sourceKey][key],sourceKey就是_data

// src/core/instance/state.js

const sharedPropertyDefinition = {
     
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function proxy (target: Object, sourceKey: string, key: string) {
     
  // 我们访问 `vm.key`,也就是访问 this[sourceKey][key],sourceKey就是_data
  sharedPropertyDefinition.get = function proxyGetter () {
     
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
     
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

Object.defineProperty详解

测试

import Vue form 'vue'

var app = new Vue({
     
	el: '#app',
	data () {
     
		message: 'hello word'
	},
	mounted () {
     
		// 调用 this.message 的时候其实是调用 this._data.message,但是因为下划线开头的是内部属性,我们不要在外部访问
		console.log(this.message)
		console.log(this._data.message)
	}
})

你可能感兴趣的:(vue)