vue源码解析--数据代理

一、前言

首先看一段代码:

var vm = new Vue({
    el: '#app',
    data: {
        name:'张三'
    },
    methods: {
        getName() {
            console.log(this.name);
        }
    }
})

这段vue代码很简单,调用getName()方法,控制台就会输出“张三”,有没有同学想过,为什么this.name就能访问到data中的name呢?
接下来我们就从vue源码中找到答案

二、new Vue() 发生了什么?

我们都知道,new 关键字在 Javascript 语言中代表实例化是一个对象,而 Vue 实际上是一个类,类在 Javascript 中是用 Function 来实现的,来看一下源码,在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 只能通过 new 关键字初始化,然后会调用 this._init 方法, 该方法在 src/core/instance/init.js 中定义。

Vue.prototype._init = function (options?: Object) {
  const vm: Component = this
  // a uid
  vm._uid = uid++

  let startTag, endTag
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    startTag = `vue-perf-start:${vm._uid}`
    endTag = `vue-perf-end:${vm._uid}`
    mark(startTag)
  }

  // a flag to avoid this being observed
  vm._isVue = true
  // merge options
  if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options)
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    )
  }
  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    initProxy(vm)
  } else {
    vm._renderProxy = vm
  }
  // expose real self
  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')

  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(`vue ${vm._name} init`, startTag, endTag)
  }

  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等

三、初始化data数据

在new Vue 时,调用了initState(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)
  //  判断opts中有data时,调用 initData(vm)
  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)
  }
}

判断opts中有data时,调用 initData(vm),方法定义如下:

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)) {
    // 循环data中的数据,并做代理
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

四、数据代理

在数据初始化时,调用了proxy(vm, ‘_data’, key),我们详细看一下这个方法:

// 定义属性的公共配置
const sharedPropertyDefinition = {
  enumerable: true,  // 可枚举
  configurable: true,  // 可重新定义
  get: noop,   // 赋值空函数
  set: noop    // 赋值空函数
}

//  target 是vue实例;
//  sourceKey传入的是字符串'_data', vm._data是vue实例的私有属性,也是初始化传入options中的data的一个映射
// key 是传入的data中的键名;
export function proxy (target: Object, sourceKey: string, key: string) {
  //  定义get方法
  // 当使用this[key]时,就会调用get方法,此时得到的是_data中对应的值
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  //  定义set方法
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  // 在vue实例对象上定义对应键值对
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

理解 proxy() 方法后,回到最初:当访问 this.name 时,其实就是访问vue实例的,私有属性_data中的name数据;

你可能感兴趣的:(javaScript,vue源码,vue.js,javascript,前端)