人人都能懂的Vue源碼系列—02—Vue構造函數

上篇博文中說到了Vue源碼的目錄結構是什麼樣的,每一個目錄的做用咱們應該也有所瞭解。咱們知道core/instance目錄主要是用來實例化Vue對象,因此咱們在這個目錄下面去尋找Vue構造函數。果真咱們找到了Vue的構造函數定義。vue

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實例時候,會判斷若是當前的環境不是生產環境,且你在調用Vue的時候,沒有用new操做符。就會調用warn函數,拋出一個警告。告訴你Vue是一個構造函數,須要用new操做符去調用。這個warn函數不是單純的console.warn,它的實現咱們後面的博文會介紹。
接下來,把options做爲參數調用_init方法。options不作過多的介紹了,就是你調用new Vue時候傳入的參數。在深刻_init方法以前,咱們先把目光移到index.js文件裏segmentfault

function Vue (options) {
  ...
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

在Vue的構造函數定義以後,有一系列方法會被當即調用。這些方法主要用來給Vue函數添加一些原型屬性和方法的。其中就有接下來要介紹的Vue.prototyoe._init緩存

Vue.prototype._init

在core/instance/init.js中咱們找到了_init的定義。代碼已經作了一些中文註釋ide

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
    // 有子組件時,options._isComponent纔會爲true
    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.
      // 優化組件實例,由於動態選項合併很慢,而且也沒有組件的選項須要特殊對待
      // 優化components屬性
      initInternalComponent(vm, options)
    } else {
      // 傳入的options和vue自身的options進行合併
      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) // 添加slot屬性
    callHook(vm, 'beforeCreate') // 調用beforeCreate鉤子
    initState(vm) // 初始化數據,進行雙向綁定 state/props
    initProvide(vm) // resolve provide after data/props 注入provider的值到子組件中
    callHook(vm, 'created') // 調用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) // 把模板轉換成render函數
    }
  }

咱們逐一來分析上述代碼。首先緩存當前的上下文到vm變量中,方便以後調用。而後設置_uid屬性。_uid屬性是惟一的。當觸發init方法,新建Vue實例時(當渲染組件時也會觸發)uid都會遞增。
下面這段代碼主要是用來測試代碼性能的,在這個時候至關於打了一個"標記點"來測試性能。函數

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

對這部份內容感興趣的朋友們能夠點擊個人另外一篇文章Performance API查看。
接下來執行這行代碼vm._isVue = true,Vue的做者對這句話作了註釋。性能

an flag to avoid this being observed

乍看起來好像不太明白,好像是說爲了防止this被observed實例化。那這到底是什麼意思呢?咱們來看observer的代碼。測試

export function observe (value: any, asRootData: ?boolean): Observer | void {
  ...
  else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  ...
}

若是傳入值的_isVue爲ture時(即傳入的值是Vue實例自己)不會新建observer實例(這裏能夠暫時理解新建observer實例就是讓數據響應式)。
再回到init源碼部分優化

if (options && options._isComponent) {
    initInternalComponent(vm, options)
} else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
        options || {},
        vm
     )
}

當符合第一個條件是,即當前這個Vue實例是組件。則執行initInternalComponent方法。(該方法主要就是爲vm.$options添加一些屬性, 後面講到組件的時候再詳細介紹)。當符合第二個條件時,即當前Vue實例不是組件。而是實例化Vue對象時,調用mergeOptions方法。mergeOptions主要調用兩個方法,resolveConstructorOptions和mergeOptions。這兩個方法牽涉到了不少知識點,爲了咱們文章篇幅的考慮。接下來準備經過兩篇博文來介紹這兩個方法。下篇博文主要介紹resolveConstructorOptions相關的內容,涉及到原型鏈和構造函數以及部分Vue.extend的實現,敬請期待!ui

相關文章
相關標籤/搜索