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的方式訪問了。

相關文章
相關標籤/搜索