vue源码(一) this.data是怎么来的?

0.获取源码

https://github.com/vuejs/vuejavascript

从github地址,直接download下来就好了。在新建项目的时候也能够node_modelus里的vue搭配着看。html

1.数据的挂载

首先先引入vue,而后新建他的实例。vue

import Vue from 'vue'
var app = new Vue({
  el:'#app',
  data:{
    return {
        message:"hello world!"  
    }
    }
})

首先咱们得知道咱们引入 的是个什么东西。因此咱们找到源码./src/core/instance/index.js里,找到了vue的庐山真面目了,其实vue就是一个类。java

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

首先process.env.NODE_ENV是判断你启动时候的参数的,若是不符合的话,就发出警告,不然执行_init方法。值得一提的是通常属性名前面加_默认表明是私有属性,不对外展现。固然若是你打印vue实例的话仍是能看见,由于只是_是私有属性人们约定俗成的,没有js语言层面的私有。node

那么这个_init是哪来的呢?往下看:git

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

能够看到下面有一大串Mixin,咱们挑第一个initMixin,而后去查看他的定义。vscode能够直接右键,而后选择转到定义 或者直接command加鼠标左键点击函数名称就能够跳过去看到定义这个方法的地方。github

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

   //..

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

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

​ init就在最开头app

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

   //..

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

​ init具体包括啥呢,首先将this上下文传给vm这个对象,而后设置_uid而后再机型一系列的初始化的工做。而后再合并options,最后挂载到vm上。
​ 可能有人会好奇,在形参部分,Vue: Class<Component>是什么意思,由于JavaScript是一个动态类型语言,也就是说,声明变量的时候不会指派他是任何一种类型的语言,像java就是典型的静态类型语言。例如:boolean result = true就是声明result是一个布尔类型,而相对的,JavaScript中能够声明var result =true。这样虽然方便不少,可是由于静态类型在编译过程当中就会查出错误并提示开发者改正错误,可是像Js这样的动态语言在编译的时候既是存在错误也不会提出,只有在真正运行时才会出错。因此就会有没必要要的麻烦,那么如何对Js进行静态类型检查呢?就是插件呗。vue用了flow的插件,让js有了静态类型检查,:后面表明了限定vue这个形参的属性。具体就不展开了,能够去看flow的文档。ide

Flow:https://flow.org/函数

​ 接下来接着说正文,const vm: Component = this能够看到把当前的执行先后文给了vm。而后以后就是一些陆陆续续的挂载,值得注意的就是vm.$options就是填写在vue实例里的参数,例如el,mounted,data都被保存在$options里。

可是日常使用的时候咱们没有用到this.$options.data1里,反而是直接用this.data1来调用,这其实vue也在其中进行了操做。

咱们会发如今上面的代码段里有一行initState(vm),咱们找到initState的定义。

export function initState (vm: Component) {
    // ..
  const opts = vm.$options
  if (opts.data) {
    initData(vm)
  } 
  // ..
}

而后咱们能够接着转到initData这个方法的定义

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

把上面的代码拆分来看

let data = vm.$options.data
 data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}

上面代码先经过$options获取到data,而后判断data是否是经过返回对象的方式创建的,若是是,那么则执行getData方法。getData的方法主要操做就是 data.call(vm, vm) 这步经过给data调用了vm这个上下文环境,而后直接返回这个包括data的vm对象。

那么如今vm上已经有data了是吗?确实,可是这个data是vm._data也就是说若是你想访问message这个属性你如今只能经过vue._data.message这样来访问。因此咱们接着往下看。

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

这一大段上面聚焦的是prop data methods 们若是相同以后就会提出相应的警示。为何要他们不同呢,由于他们都是经过this.XX来调用的,若是重名,vue分不清他们是谁。若是都没问题了,咱们就把_datas上的值直接赋给vm,而后转到最后一步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)
}

就是经过sharedPropertyDefinition.getsharedPropertyDefinition.set的设置的get和set方法,而后在经过Object.defineProperty来定义访问target.key的时候调用sharedPropertyDefinition的set和get。

也就是至关于,我要求vm.message,就会触发sharedPropertyDefinition的get,而后返回vm._data.message

至此数据就能够经过vm.message的方式访问了。

相关文章
相关标签/搜索