Vue源碼分析之Vue實(shí)例初始化詳解
這一節(jié)主要記錄一下:Vue 的初始化過程
以下正式開始:
Vue官網(wǎng)的生命周期圖示表

重點(diǎn)說一下 new Vue()后的初始化階段,也就是created之前發(fā)生了什么。

initLifecycle 階段
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm) // 自己把自己添加到父級的$children數(shù)組中
}
vm.$parent = parent // 父組件實(shí)例
vm.$root = parent ? parent.$root : vm // 根組件 如果不存在父組件,則本身就是根組件
vm.$children = [] // 用來存放子組件
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
接下來是initEvents 階段
// v-on如果寫在平臺標(biāo)簽上如:div,則會將v-on上注冊的事件注冊到瀏覽器事件中
// v-on如果寫在組件標(biāo)簽上,則會將v-on注冊的事件注冊到子組件的事件系統(tǒng)
// 子組件(Vue實(shí)例)在初始化的時(shí)候,有可能接收到父組件向子組件注冊的事件。
// 子組件(Vue實(shí)例)自身模板注冊的事件,只要在渲染的時(shí)候才會根據(jù)虛擬DOM的對比結(jié)果
// 來確定是注冊事件還是解綁事件
// 這里初始化的事件是指父組件在模板中使用v-on注冊的事件添加到子組件的事件系統(tǒng)也就是vue的事件系統(tǒng)。
export function initEvents (vm: Component) {
vm._events = Object.create(null) // 初始化
vm._hasHookEvent = false
// init parent attached events 初初始化腹肌組件添加的事件
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
initjections 階段
export function initInjections (vm: Component) {
// 自下而上讀取inject
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 設(shè)置為false 避免defineReactive函數(shù)把數(shù)據(jù)轉(zhuǎn)換為響應(yīng)式
toggleObserving(false)
Object.keys(result).forEach(key => {
defineReactive(vm, key, result[key])
})
// 再次更改回來
toggleObserving(true)
}
}
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
// 如果瀏覽器支持Symbol,則使用Reflect.ownkyes(),否則使用Object.keys()
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
const provideKey = inject[key].from
let source = vm
// 當(dāng)provided注入內(nèi)容的時(shí)候就是把內(nèi)容注入到當(dāng)前實(shí)例的_provided中
// 剛開始的時(shí)候 source就是實(shí)本身,擋在source._provided中找不到對應(yīng)的值
// 就會把source設(shè)置為父實(shí)例
// Vue實(shí)例化的第一步就是規(guī)格化用戶傳入的數(shù)據(jù),所以inject不管時(shí)數(shù)組還是對象
// 最后都會變成對象
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
// 處理默認(rèn)值的情況
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}
initState 階段
在 Vue 中,我們經(jīng)常會用到 props 、methods 、 watch 、computed 、data 。這些狀態(tài)在使用前都需要初始化。而初始化的過程正是在 initState 階段完成。
因?yàn)?injects 是在 initState 之前完成,所以可以在 State 中使用 injects 。
export function initState (vm: Component) {
vm._watchers = []
// 獲取到經(jīng)過初始化的用戶傳進(jìn)來的options
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProps
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
// 緩存props的key值
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
// 如果不是跟組件則沒必要轉(zhuǎn)換成響應(yīng)式數(shù)據(jù)
if (!isRoot) {
// 控制是否轉(zhuǎn)換成響應(yīng)式數(shù)據(jù)
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
// 獲取props的值
const value = validateProp(key, propsOptions, propsData, vm)
defineReactive(props, key, value)
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
// 把props代理到Vue實(shí)例上來,可以直接通過this.props訪問
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
initMethods
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
// 如果key不是一個(gè)函數(shù) 報(bào)錯(cuò)
if (typeof methods[key] !== 'function') {
warn(
`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
// 如果props中存在同名的屬性 報(bào)錯(cuò)
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
// isReserved判斷是否以$或_開頭
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
// 把methods的方法綁定到Vue實(shí)例上
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
initData
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// isPlainObject監(jiān)測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
// 循環(huán)data
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
// 如果在methods中存在和key同名的屬性 則報(bào)錯(cuò)
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 如果在props中存在和key同名的屬性 則報(bào)錯(cuò)
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)) {
// isReserved判斷是否以$或_開頭
// 代理data,使得可以直接通過this.key訪問this._data.key
proxy(vm, `_data`, key)
}
}
// observe data
// 把data轉(zhuǎn)換為響應(yīng)式數(shù)據(jù)
observe(data, true /* asRootData */)
}
initComputed
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
// 判斷是不是服務(wù)端渲染
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
// 如果不是ssr,則創(chuàng)建Watcher實(shí)例
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
// 如果vm不存在key的同名屬性
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
sharedPropertyDefinition = {
enumerable: true,
cnfigurable: true,
get: noop,
set: noop
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
// 如果是服務(wù)端渲染,則computed不會有緩存,因?yàn)閿?shù)據(jù)響應(yīng)式的過程在服務(wù)器是多余的
const shouldCache = !isServerRendering()
// createComputedGetter返回計(jì)算屬性的getter
// createGetterInvoker返回userDef的getter
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
// 當(dāng)userDef為一個(gè)對象時(shí)
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
// 在tearget上定義一個(gè)屬性, 屬性名為key, 屬性描述符為sharedPropertyDefinition
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
// 查找是否存在key的Watcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 如果dirty為true,則重新計(jì)算,否則返回緩存
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
initWatch
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
// 處理數(shù)組類型
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
// isPlainObject檢查是否是對象
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
// 最后調(diào)用$watch
return vm.$watch(expOrFn, handler, options)
}
initProvide階段
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
// 把provided存到_provided上
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
到這里 Vue 的初始化就結(jié)束了,接下來就是觸發(fā)生命周期函數(shù) created 。
總結(jié)一下:new Vue() 執(zhí)行之后,Vue 進(jìn)入初始化階段。
初始化流程如下:
- 規(guī)格化 $options ,也就是用戶自定義的數(shù)據(jù)
- initLifecycle 注入生命周期
- initEvents 初始化事件,注意:這里的事件是值在父組件在子組件上定義的事件
- initRender
- initjections 初始化 jetction
- initProps 初始化props
- initState 包括props 、methods 、data 、computed 、watch
- initProvided 初始化 provide
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。
相關(guān)文章
element step組件在另一側(cè)加時(shí)間軸顯示
本文主要介紹了element step組件在另一側(cè)加時(shí)間軸顯示,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
Vue 2.0在IE11中打開項(xiàng)目頁面空白的問題解決
這篇文章主要給大家介紹了關(guān)于Vue 2.0在ie 11中打開項(xiàng)目頁面空白問題的解決方法,文中詳細(xì)分析出現(xiàn)該問題的原因,并給出了詳細(xì)的解決方法,需要的朋友可以參考借鑒,下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-07-07
Vue3?pinia管理數(shù)據(jù)的3種方式代碼
在Vue3中Pinia是一個(gè)狀態(tài)管理庫,它提供了一種簡單而強(qiáng)大的方式來管理應(yīng)用程序的狀態(tài),這篇文章主要給大家介紹了關(guān)于Vue3?pinia管理數(shù)據(jù)的3種方式,需要的朋友可以參考下2024-04-04
vue項(xiàng)目創(chuàng)建步驟及路由router
本文主要給大家分享了vue項(xiàng)目的創(chuàng)建步驟以及vue路由router的相關(guān)知識點(diǎn),非常的實(shí)用,有需要的小伙伴可以來參考下2020-01-01
elementui[el-table]toggleRowSelection默認(rèn)多選事件無法選中問題
這篇文章主要介紹了elementui[el-table]toggleRowSelection默認(rèn)多選事件無法選中問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
vue router+vuex實(shí)現(xiàn)首頁登錄驗(yàn)證判斷邏輯
這篇文章主要介紹了vue router+vuex實(shí)現(xiàn)首頁登錄判斷邏輯,用于判斷是否登錄首頁,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05

