隨着初始化函數的執行,實例的生命週期也開始運轉,在初始化函數裏能夠看到每一個模塊向實例集成的功能,這些功能的具體內容之後在單獨的文章裏繼續探索。如今來詳細看看類初始化函數的詳細代碼。html
*下面代碼位於vue/src/core/instance/init.jsvue
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
複製代碼
頭部注入的一些方法是在生命週期運行中開始初始化的功能,以前在覈心類實現的文章中有提到過,在這裏不展開。config
對象是做爲基本的配置參數,在不一樣運行環境裏會更改當中的屬性值來適應不一樣的平臺需求,在這個文件中只用到了其中的性能檢測屬性,與具體的類的實現沒有太大關聯,與引入的mark
、measure
、formatComponentName
方法配合主要是作性能評估用的。node
在初始化組件的時候主要用到的是工具方法extend
、mergeOptions
。git
extend
函數是一個很簡單的爲對象擴展屬性的方法,代碼位於這個文件中vue/src/shared/util.js,具體實現很是基礎,看看就好。github
/** * Mix properties into target object. */
export function extend (to: Object, _from: ?Object): Object {
for (const key in _from) {
to[key] = _from[key]
}
return to
}
複製代碼
mergeOptions
函數代碼位於 vue/src/core/util/options.js中,它是初始化合並options對象時很是重要的函數,爲了看明白它在初始化函數裏的用途,稍微花點時間來仔細看一下它的具體實現。數組
// 該函數用於將兩個配置對象合併爲一個新的配置對象,
// 核心實體既用於實例化也用於繼承
/** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */
// 導出mergeOptions函數
// 接收Object類型的parent、child參數,Component類型的vm參數
// 函數返回對象
export function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object {
// 非生產環境時檢查child對象的components屬性中是否有不合適的引用組件名稱
// 不合適的組建名主要是指與Vue內建html標籤或保留標籤名相同的組件名稱如slot,component
// 有興趣瞭解的能夠參照同一文件中的L246到L269查看具體實現
// 其中的輔助工具函數位於src/shared/util.js的L94到L112
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
// 若是child傳入的是函數對象,則將函數的options屬性賦值給child,確保child引用options
if (typeof child === 'function') {
child = child.options
}
// 下面三個函數都是將child的各個屬性格式化成預約好的對象格式
// 標準化屬性
normalizeProps(child, vm)
// 標準化注入
normalizeInject(child, vm)
// 標準化指令
normalizeDirectives(child)
// 定義擴展
const extendsFrom = child.extends
// 若是存在則向下遞歸合併
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
// 若是存在mixins,則合併每個mixin對象
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
// 定義以空對象options
const options = {}
let key
// 對每個parent中的屬性進行合併,添加到options中
for (key in parent) {
mergeField(key)
}
// 若是parent中不含有key屬性,則對每個child中key屬性進行合併
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
// 定義mergeField函數,接收key參數
function mergeField (key) {
// 若是strats[key]有定義好的合併策略函數,則複製給strat
// 不然將默認的defaultStrat方法賦給strat
const strat = strats[key] || defaultStrat
// 合併屬性
options[key] = strat(parent[key], child[key], vm, key)
}
// 返回最終options對象
return options
}
複製代碼
儘管 mergeOptions
函數的實現有些複雜,但它的做用其實比較明確,就是解決初始化的過程當中對繼承的類的options對象和新傳入的options對象之間同名屬性的衝突,即便用繼承的屬性值仍是新傳入的屬性值的問題。在代碼的一開始官方就已說明它是一個遞歸函數,能夠一併解決添加了擴展內容和使用了mixins的場景,總而言之,這個步驟就是確保咱們初始化的實例的options對象正確惟一。ide
代碼中有幾個標準化屬性的函數,具體實現也在以上代碼的同一文件中,雖然有一堆代碼,但實現仍是比較簡單,主要目的就是把傳入的options對象的各個屬性格式化成基於對象的預約格式,在之後的運行中方便使用。函數
hasOwn
函數是對 Object.prototype.hasOwnProperty
方法的一個包裝,比較簡單,須要瞭解的話就去util工具函數文件中查看。工具
值得一提的是 strats
的使用。在代碼的一開始的部分就定義 strats
變量,並說明它是用來處理父子選項合併屬性的功能。性能
/** * Option overwriting strategies are functions that handle * how to merge a parent option value and a child option * value into the final value. */
const strats = config.optionMergeStrategies
複製代碼
對於 el
和 propsData
屬性的合併策略賦予 defaultStrat
函數,該函數的原則是child對象屬性優先,沒有child對象屬性則返回parent的對應屬性。
/** * Options with restrictions */
if (process.env.NODE_ENV !== 'production') {
strats.el = strats.propsData = function (parent, child, vm, key) {
if (!vm) {
warn(
`option "${key}" can only be used during instance ` +
'creation with the `new` keyword.'
)
}
return defaultStrat(parent, child)
}
}
複製代碼
data
、watch
、props
、methods
、inject
、computed
、provide
、各類鉤子函數和ASSET_TYPES
裏包含的component
、directive
、 filter
三個屬性都分別定義了相關的合併方法,有興趣繼續瞭解的同窗能夠在同一分文件中查看,代碼太長可是實現比較基礎,因此沒什麼好詳說的,能夠關注一下的是某些屬性是替換覆蓋,而某些屬性是合併成數組如各類鉤子的監聽函數。
對於初始化合並options的操做分爲了兩個方向,一是建立內部組件時的合併,二是建立非內部組件實例時的合併,先來講說內部組件初始化的詳細內容,在類的實現中對應着這一句代碼 initInternalComponent(vm, options)
// 輸出initInternalComponent函數
// 接受Component類型的vm參數和InternalComponentOptions類型的options參數
// 這裏vm和options分別是建立實例時將要傳入的實例對象和配置對象
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// 定義opts,爲opts和vm.$options建立以vm.constructor.options爲原型的對象
const opts = vm.$options = Object.create(vm.constructor.options)
// 如下爲手動賦值,目的爲了提高性能,由於比經過動態枚舉屬性來賦值的過程快
// doing this because it's faster than dynamic enumeration.
// 定義父虛擬節點parentVnode並賦值
const parentVnode = options._parentVnode
// 設置opts對象parent和_parentVnode屬性
opts.parent = options.parent
opts._parentVnode = parentVnode
// 定義vnodeComponentOptions並賦值
const vnodeComponentOptions = parentVnode.componentOptions
// 定義opts各屬性
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
// options.render屬性存在,則設置opts的render和staticRenderFns屬性
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
複製代碼
能夠看出 initInternalComponent
函數的內容比較簡單,主要是爲建立的內部組件的options對象手動賦值,提高性能,由於按照官方註釋的說法是全部的內部組件的初始化都沒有列外能夠一樣處理。至於何時會建立內部組件,這種場景目前還不太瞭解,能肯定的是一般建立Vue實例來初始化視圖頁面的用法是非內部組件性質的。在這裏留下一個疑問。
下面三個函數就是初始化實例合併options這條線時用到的方法,後兩個函數做輔助用。對應以下帶代碼。主要是用來解決構造函數的默認配置選項和擴展選項之間的合併問題。
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
複製代碼
// 導出resolveConstructorOptions函數,接受Component構造函數Ctor參數
export function resolveConstructorOptions (Ctor: Class<Component>) {
// 定義傳入的構造函數的options屬性
let options = Ctor.options
// 若是Ctor.super存在則執行下面代碼,這裏是用來判斷實例對象是否有繼承
// 若是有的話遞歸的把繼承的父級對象的options都拿出來合併
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// 若是父級的option變化了則要更新
// 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)
// 若是返回有修改的選項,則擴展Ctor.extendOptions
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
// 合併繼承選項和擴展選項
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
// 設置options.components[options.name]的引用
if (options.name) {
options.components[options.name] = Ctor
}
}
}
// 返回options
return options
}
// 如下兩個函數是爲了解決#4976問題的方案
// 定義resolveModifiedOptions函數,接受Ctor參數,返回Object
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
// 定義modified變量儲存最終要選擇保留的屬性
let modified
// 分別定義最新、擴展和密封的配置選項
const latest = Ctor.options
const extended = Ctor.extendOptions
const sealed = Ctor.sealedOptions
// 遍歷傳入的配置選項對象
for (const key in latest) {
// 若是最新的屬性與密封的屬性不相等,則執行去重處理
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = dedupe(latest[key], extended[key], sealed[key])
}
}
// 返回modified
return modified
}
// 定義dedupe函數,接收latest最新對象,extended擴展對象,sealed密封對象
function dedupe (latest, extended, sealed) {
// 合併選項的時候比較最新和密封的屬性,確保生命週期鉤子不重複
// compare latest and sealed to ensure lifecycle hooks won't be duplicated
// between merges
// 若是latest是數組
if (Array.isArray(latest)) {
// 定義res變量
const res = []
// 格式化sealed和extended爲數組對象
sealed = Array.isArray(sealed) ? sealed : [sealed]
extended = Array.isArray(extended) ? extended : [extended]
for (let i = 0; i < latest.length; i++) {
// 返回擴展的選項中存在的最新對象的屬性而非密封選項以排除重複選項
// push original options and not sealed options to exclude duplicated options
if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
res.push(latest[i])
}
}
// 返回包含了擴展選項的數組變量
return res
} else {
// 不然直接返回latest
return latest
}
}
複製代碼
使用 resolveConstructorOptions
函數解決了繼承的構造函數的選項以後,新建立的實例vm的$options對象就是繼承選項和建立時傳入的options選項的合併。其中雖然有不少複雜的遞歸調用,可是這些函數的目的都是爲了肯定最終的選項,理解這個目的很是重要。
初始化函數的執行不只在於開始生命週期的運行,對於options對象的各個屬性值如何取捨的問題給出了很是複雜但健全的解決方法,這爲生命週期正常運行鋪墊了很是堅實的基礎,有了清晰的options選項,以後的功能才能如期順利執行。在這裏也能夠看出Vue處理各類屬性的合併原則,對此有良好的理解能夠確保在使用時當即定位遇到的相關問題。