Vue源碼 - 入口文件分析

我們都知道,從一個new Vue開始,就說明一個Vue項目的開始:

new Vue({
  el: ...,
  data: ...,
  ....
})

那麼在這次實例化的過程中,究竟發生了哪些行爲呢?

Vue的源碼文件,其核心代碼在src/core目錄下。下面我們從入口文件index.js開始進入:

/ src/core/index.js

// 這裏是我們 Vue 核心方法
import Vue from './instance/index'
// 根據命名,應該可以猜出這裏是初始化一些全局API
import { initGlobalAPI } from './global-api/index'
// 根據命名,這裏應該是獲取一個Boolean類型的變量,來判斷是不是ssr
import { isServerRendering } from 'core/util/env'
// 這裏開始執行初始化全局變量
initGlobalAPI(Vue)
// 爲Vue原型定義屬性$isServer
Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})
// 爲Vue原型定義屬性$ssrContext
Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

Vue.version = '__VERSION__'

export default Vue

首先找到core/instance/index文件,可以清晰的看到

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

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

export default Vue

這裏簡單粗暴的定義了一個 Vue Class,然後又調用了一系列init、mixin這樣的方法來初始化一些功能,具體的我們後面在分析,不過通過代碼我們可以確認的是這裏確實是導出了一個 Vue 功能類。

接下來,我們接着看initGlobalAPI這個東西,其實在Vue官網上,就已經爲我們說明了Vue的全局屬性:

// core/global-api/index

...
export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // 這些工具方法不視作全局API的一部分,除非你已經意識到某些風險,否則不要去依賴他們
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }
  // 這裏定義全局屬性
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
  Vue.options._base = Vue
  extend(Vue.options.components, builtInComponents)

  // 定義全局方法
  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}
  • 【Vue.config】 各種全局配置項

  • 【Vue.util】 各種工具函數,還有一些兼容性的標誌位(哇,不用自己判斷瀏覽器了,Vue已經判斷好了)

  • 【Vue.set/delete】 這個你文檔應該見過

  • 【Vue.nextTick】

  • 【Vue.options】 這個options和我們上面用來構造實例的options不一樣。這個是Vue默認提供的資源(組件指令過濾器)。

  • 【Vue.use】 通過initUse方法定義

  • 【Vue.mixin】 通過initMixin方法定義

  • 【Vue.extend】通過initExtend方法定義

到這裏,我們的入口文件差不多就瞭解清楚了。

那當我們實例化一個Vue對象的時候,經歷了些什麼呢?還是以Vue源碼來說明問題吧,打開我們的core/instance/index文件

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

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

export default Vue

 

首先執行的是initMixin(Vue)方法,從名字來猜測它的功能應該是要混入一些功能。具體實現代碼如下:

// core/instance/init.js
...
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
      const vm: Component = this
      vm._uid = uid++
      // 如果是Vue的實例,則不需要被observe
      vm._isVue = true
      // 第一步: 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
        )
      }
      // 第二步: renderProxy
      if (process.env.NODE_ENV !== 'production') {
        initProxy(vm)
      } else {
        vm._renderProxy = vm
      }
      // expose real self
      vm._self = vm

      // 第三步: vm的生命週期相關變量初始化
      initLifecycle(vm)

      // 第四步: vm的事件監聽初始化
      initEvents(vm)
      callHook(vm, 'beforeCreate')
      initInjections(vm) // resolve injections before data/props

      // 第五步: vm的狀態初始化,prop/data/computed/method/watch都在這裏完成初始化,因此也是Vue實例create的關鍵。
      initState(vm)
      initProvide(vm) // resolve provide after data/props
      callHook(vm, 'created')

      // 第六步:render & mount
      if (vm.$options.el) {
        vm.$mount(vm.$options.el)
      }
  }
}

一眼看過去,其實也就知道了,主要是爲我們的Vue原型上定義一個方法_init。然後當我們執行new Vue(options) 的時候,會調用這個方法。而這個_init方法的實現,便是我們需要關注的地方。 前面定義vm實例都挺好理解的,主要我們來看一下mergeOptions這個方法,其實我們的Vue實例,會在代碼運行後增加很多新的東西進去。我們把我們傳入的這個對象叫options,實例中我們可以通過vm.$options訪問到。

 

mergeOptions主要分成兩塊,就是resolveConstructorOptions(vm.constructor)options,mergeOptions這個函數的功能就是要把這兩個合在一起。options是我們通過new Vue(options)實例化傳入的,所以,我們主要需要調研的是resolveConstructorOptions這個函數的功能。

resolveConstructorOptions 處理 options

通過函數的命名大致能推測出他的功能應該是解析構造函數的options,通過我們出入的參數vm.constructor,也可以看出來這一點。好了,代碼實現如下:

export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

 
Vue.extend = function (extendOptions: Object): Function {
  ...
  const Sub = function VueComponent (options) {
    this._init(options)
  }
  Sub.prototype = Object.create(Super.prototype)
  Sub.prototype.constructor = Sub
  Sub.cid = cid++
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )
  Sub['super'] = Super

  // For props and computed properties, we define the proxy getters on
  // the Vue instances at extension time, on the extended prototype. This
  // avoids Object.defineProperty calls for each instance created.
  if (Sub.options.props) {
    initProps(Sub)
  }
  if (Sub.options.computed) {
    initComputed(Sub)
  }

  // allow further extension/mixin/plugin usage
  Sub.extend = Super.extend
  Sub.mixin = Super.mixin
  Sub.use = Super.use

  // create asset registers, so extended classes
  // can have their private assets too.
  ASSET_TYPES.forEach(function (type) {
    Sub[type] = Super[type]
  })
  // enable recursive self-lookup
  if (name) {
    Sub.options.components[name] = Sub
  }

  // keep a reference to the super options at extension time.
  // later at instantiation we can check if Super's options have
  // been updated.
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend({}, Sub.options)

  // cache constructor
  cachedCtors[SuperId] = Sub
  return Sub
}

我們可以看到,該函數,實現了一個JS的經典繼承方法,最後返回了一個繼承自Super 的子類Sub。我們主要注意這裏的代碼:

  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )
  Sub['super'] = Super
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend({}, Sub.options)

看到這裏,上面的resolveConstructorOptions功能函數我們就大致明白什麼意思了:

1. Ctor.super 來判斷該類是否是Vue的子類

2. if (superOptions !== cachedSuperOptions) 來判斷父類中的options 有沒有發生變化,主要考慮到下面這種情況:

Vue.extend(options)
Vue.mixin(options)

當爲Vue混入一些options時,superOptions會發生變化,此時於之前子類中存儲的cachedSuperOptions已經不等,所以下面的操作主要就是更新sub.superOptions

3. 返回獲merge自己的options與父類的options屬性

接下來就重點看mergeOptions的實現了:

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  //...

  // 統一props格式
  normalizeProps(child)
  // 統一directives的格式
  normalizeDirectives(child)

  // 如果存在child.extends
  // ...
  // 如果存在child.mixins
  // ...

  // 針對不同的鍵值,採用不同的merge策略
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

上面採取了對不同的field採取不同的策略,Vue提供了一個strats對象,它本身就是一個hook,如果strats有提供特殊的邏輯,就走strats,否則走默認merge邏輯。

const strats = config.optionMergeStrategies
strats.el  = strats.propsData = ...
strats.data = ...
strats.watch  ...
....

const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

用這種hook的方式就能很好的區分對待公共處理邏輯與特殊處理邏輯,我們以data爲例去看看它們做了什麼特殊處理:

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  /**
  * Vue.extend 方法裏面是這麼合併屬性的:
  * Sub.options = mergeOptions(
  *   Super.options,
  *   extendOptions
  * )
  * 在Vue的組件繼承樹上的merge是不存在vm的
  */
  if (!vm) {
    // 如果子屬性不是個函數,那麼返回父屬性的值
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )

      return parentVal
    }
    return mergeDataOrFn.call(this, parentVal, childVal)
  }

  return mergeDataOrFn(parentVal, childVal, vm)
}


/**
* 接下來看看mergeDataOrFn操作,如果options.data是個函數,主要是執行函數後,再進行data的merge
*/
export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    // when parentVal & childVal are both present,
    // we need to return a function that returns the
    // merged result of both functions... no need to
    // check if parentVal is a function here because
    // it has to be a function to pass previous merges.
    return function mergedDataFn () {
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this) : parentVal
      )
    }
  } else if (parentVal || childVal) {
    return function mergedInstanceDataFn () {
      // instance merge
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm)
        : childVal
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm)
        : parentVal
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

這裏說的是,options.data經過merge之後,實際上是一個function,在真正調用function纔會進行真正的merge,其它的merge都會根據自身特點而又不同的操作,這裏就不貼代碼了。

走到這一步,我們終於把邏輯以及組件的一些特性全都放到了vm.$options中了,後續的操作我們都可以從vm.$options拿到可用的信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章