1. 前言
本篇文章介紹生命周期初始化階段所調(diào)用的第五個初始化函數(shù)——initState
。 從函數(shù)名字上來看,這個函數(shù)是用來初始化實例狀態(tài)的,那么什么是實例的狀態(tài)呢?在前面文章中我們略有提及,在我們?nèi)粘i_發(fā)中,在Vue
組件中會寫一些如props
、data
、methods
、computed
、watch
選項,我們把這些選項稱為實例的狀態(tài)選項。也就是說,initState
函數(shù)就是用來初始化這些狀態(tài)的,那么接下來我們就來分析該函數(shù)是如何初始化這些狀態(tài)選項的。
2. initState函數(shù)分析
首先我們先來分析initState
函數(shù),該函數(shù)的定義位于源碼的src/core/instance/state.js
中,如下:
export function initState (vm: Component) {
vm._watchers = []
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)
}
}
可以看到,該函數(shù)的代碼并不多,而且邏輯也非常清晰。
首先,給實例上新增了一個屬性_watchers
,用來存儲當前實例中所有的watcher
實例,無論是使用vm.$watch
注冊的watcher
實例還是使用watch
選項注冊的watcher
實例,都會被保存到該屬性中。
這里我們再額外多說一點,在變化偵測篇中我們介紹了Vue
中對數(shù)據(jù)變化的偵測是使用屬性攔截的方式實現(xiàn)的,但是Vue
并不是對所有數(shù)據(jù)都使用屬性攔截的方式偵測變化,這是因為數(shù)據(jù)越多,數(shù)據(jù)上所綁定的依賴就會多,從而造成依賴追蹤的內(nèi)存開銷就會很大,所以從Vue 2.0
版本起,Vue
不再對所有數(shù)據(jù)都進行偵測,而是將偵測粒度提高到了組件層面,對每個組件進行偵測,所以在每個組件上新增了vm._watchers
屬性,用來存放這個組件內(nèi)用到的所有狀態(tài)的依賴,當其中一個狀態(tài)發(fā)生變化時,就會通知到組件,然后由組件內(nèi)部使用虛擬DOM
進行數(shù)據(jù)比對,從而降低內(nèi)存開銷,提高性能。
繼續(xù)回到源碼,接下來就是判斷實例中有哪些選項就調(diào)用對應(yīng)的選項初始化子函數(shù)進行初始化,如下:
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)
}
先判斷實例中是否有props
選項,如果有,就調(diào)用props
選項初始化函數(shù)initProps
去初始化props
選項;
再判斷實例中是否有methods
選項,如果有,就調(diào)用methods
選項初始化函數(shù)initMethods
去初始化methods
選項;
接著再判斷實例中是否有data
選項,如果有,就調(diào)用data
選項初始化函數(shù)initData
去初始化data
選項;如果沒有,就把data
當作空對象并將其轉(zhuǎn)換成響應(yīng)式;
接著再判斷實例中是否有computed
選項,如果有,就調(diào)用computed
選項初始化函數(shù)initComputed
去初始化computed
選項;
最后判斷實例中是否有watch
選項,如果有,就調(diào)用watch
選項初始化函數(shù)initWatch
去初始化watch
選項;
總之一句話就是:有什么選項就調(diào)用對應(yīng)的選項初始化子函數(shù)去初始化什么選項。
以上就是initState
函數(shù)的所有邏輯,其實你會發(fā)現(xiàn),在函數(shù)內(nèi)部初始化這5個選項的時候它的順序是有意安排的,不是毫無章法的。如果你在開發(fā)中有注意到我們在data
中可以使用props
,在watch
中可以觀察data
和props
,之所以可以這樣做,就是因為在初始化的時候遵循了這種順序,先初始化props
,接著初始化data
,最后初始化watch
。
下面我們就針對這5個狀態(tài)選項對應(yīng)的5個初始化子函數(shù)進行逐一分析,看看其內(nèi)部分別都是如何進行初始化的。
3. 初始化props
props
選項通常是由當前組件的父級組件傳入的,當父組件在調(diào)用子組件的時候,通常會把props
屬性值作為標簽屬性添加在子組件的標簽上,如下:
<Child prop1="xxx" prop2="yyy"></Child>
在前面文章介紹初始化事件initEvents
函數(shù)的時候我們說了,在模板編譯的時候,當解析到組件標簽時會將所有的標簽屬性都解析出來然后在子組件實例化的時候傳給子組件,當然這里面就包括props
數(shù)據(jù)。
在子組件內(nèi)部,通過props
選項來接收父組件傳來的數(shù)據(jù),在接收的時候可以這樣寫:
// 寫法一
props: ['name']
// 寫法二
props: {
name: String, // [String, Number]
}
// 寫法三
props: {
name:{
type: String
}
}
可以看到,Vue
給用戶提供的props
選項寫法非常自由,根據(jù)Vue
的慣例,寫法雖多但是最終處理的時候肯定只處理一種寫法,此時你肯定會想到,處理之前先對數(shù)據(jù)進行規(guī)范化,將所有寫法都轉(zhuǎn)化成一種寫法。對,你沒有猜錯,同規(guī)范化事件一樣,在合并屬性的時候也進行了props
數(shù)據(jù)的規(guī)范化。
3.1 規(guī)范化數(shù)據(jù)
props
數(shù)據(jù)規(guī)范化函數(shù)的定義位于源碼的src/core/util/options.js
中,如下:
function normalizeProps (options, vm) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
if (Array.isArray(props)) {
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
name = camelize(val)
res[name] = { type: null }
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
for (const key in props) {
val = props[key]
name = camelize(key)
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
options.props = res
}
上面代碼中,首先拿到實例中的props
選項,如果不存在,則直接返回。
const props = options.props
if (!props) return
如果存在,則定義一個空對象res
,用來存儲最終的結(jié)果。接著判斷如果props
選項是一個數(shù)組(寫法一),則遍歷該數(shù)組中的每一項元素,如果該元素是字符串,那么先將該元素統(tǒng)一轉(zhuǎn)化成駝峰式命名,然后將該元素作為key
,將{type: null}
作為value
存入res
中;如果該元素不是字符串,則拋出異常。如下:
if (Array.isArray(props)) {
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
name = camelize(val)
res[name] = { type: null }
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.')
}
}
}
如果props
選項不是數(shù)組那就繼續(xù)判斷是不是一個對象,如果是一個對象,那就遍歷對象中的每一對鍵值,拿到每一對鍵值后,先將鍵名統(tǒng)一轉(zhuǎn)化成駝峰式命名,然后判斷值是否還是一個對象,如果值是對象(寫法三),那么就將該鍵值對存入res
中;如果值不是對象(寫法二),那么就將鍵名作為key
,將{type: null}
作為value
存入res
中。如下:
if (isPlainObject(props)) {
for (const key in props) {
val = props[key]
name = camelize(key)
res[name] = isPlainObject(val)
? val
: { type: val }
}
}
如果props
選項既不是數(shù)組也不是對象,那么如果在非生產(chǎn)環(huán)境下就拋出異常,最后將res
作為規(guī)范化后的結(jié)果重新賦值給實例的props
選項。如下:
if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
options.props = res
以上就是對props
數(shù)據(jù)的規(guī)范化處理,可以看到,無論是三種寫法的哪一種,最終都會被轉(zhuǎn)化成如下寫法:
props: {
name:{
type: xxx
}
}
3.2 initProps函數(shù)分析
將props
選項規(guī)范化完成之后,接下來我們就可以來真正的初始化props
選項了,initProps
函數(shù)的定義位于源碼的src/core/instance/state.js
中,如下:
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.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => {
if (vm.$parent && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
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.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
可以看到,該函數(shù)接收兩個參數(shù):當前Vue
實例和當前實例規(guī)范化后的props
選項。
在函數(shù)內(nèi)部首先定義了4個變量,分別是:
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
- propsData:父組件傳入的真實
props
數(shù)據(jù)。 - props:指向
vm._props
的指針,所有設(shè)置到props
變量中的屬性都會保存到vm._props
中。 - keys:指向
vm.$options._propKeys
的指針,緩存props
對象中的key
,將來更新props
時只需遍歷vm.$options._propKeys
數(shù)組即可得到所有props
的key
。 - isRoot:當前組件是否為根組件。
接著,判斷當前組件是否為根組件,如果不是,那么不需要將props
數(shù)組轉(zhuǎn)換為響應(yīng)式的,toggleObserving(false)
用來控制是否將數(shù)據(jù)轉(zhuǎn)換成響應(yīng)式。如下:
if (!isRoot) {
toggleObserving(false)
}
接著,遍歷props
選項拿到每一對鍵值,先將鍵名添加到keys
中,然后調(diào)用validateProp
函數(shù)(關(guān)于該函數(shù)下面會介紹)校驗父組件傳入的props
數(shù)據(jù)類型是否匹配并獲取到傳入的值value
,然后將鍵和值通過defineReactive
函數(shù)添加到props
(即vm._props
)中,如下:
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => {
if (vm.$parent && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
}
添加完之后再判斷這個key
在當前實例vm
中是否存在,如果不存在,則調(diào)用proxy
函數(shù)在vm
上設(shè)置一個以key
為屬性的代碼,當使用vm[key]
訪問數(shù)據(jù)時,其實訪問的是vm._props[key]
。如下:
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
以上就是initProps
函數(shù)的所有邏輯,接下來我們再看一下是如何通過validateProp
函數(shù)校驗父組件傳入的props
數(shù)據(jù)類型是否匹配并獲取到傳入的值的。
3.3 validateProp函數(shù)分析
validateProp
函數(shù)的定義位于源碼的src/core/util/props.js
中,如下:
export function validateProp (key,propOptions,propsData,vm) {
const prop = propOptions[key]
const absent = !hasOwn(propsData, key)
let value = propsData[key]
// boolean casting
const booleanIndex = getTypeIndex(Boolean, prop.type)
if (booleanIndex > -1) {
if (absent && !hasOwn(prop, 'default')) {
value = false
} else if (value === '' || value === hyphenate(key)) {
// only cast empty string / same name to boolean if
// boolean has higher priority
const stringIndex = getTypeIndex(String, prop.type)
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true
}
}
}
// check default value
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key)
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldObserve = shouldObserve
toggleObserving(true)
observe(value)
toggleObserving(prevShouldObserve)
}
if (process.env.NODE_ENV !== 'production') {
assertProp(prop, key, value, vm, absent)
}
return value
}
可以看到,該函數(shù)接收4個參數(shù),分別是:
- key:遍歷
propOptions
時拿到的每個屬性名。 - propOptions:當前實例規(guī)范化后的
props
選項。 - propsData:父組件傳入的真實
props
數(shù)據(jù)。 - vm:當前實例。
在函數(shù)內(nèi)部首先定義了3個變量,分別是:
const prop = propOptions[key]
const absent = !hasOwn(propsData, key)
let value = propsData[key]
- prop:當前
key
在propOptions
中對應(yīng)的值。 - absent:當前
key
是否在propsData
中存在,即父組件是否傳入了該屬性。 - value:當前
key
在propsData
中對應(yīng)的值,即父組件對于該屬性傳入的真實值。
接著,判斷prop
的type
屬性是否是布爾類型(Boolean),getTypeIndex
函數(shù)用于判斷prop
的type
屬性中是否存在某種類型,如果存在,則返回該類型在type
屬性中的索引(因為type
屬性可以是數(shù)組),如果不存在則返回-1。
如果是布爾類型的話,那么有兩種邊界情況需要單獨處理:
-
如果
absent
為true
,即父組件沒有傳入該prop
屬性并且該屬性也沒有默認值的時候,將該屬性值設(shè)置為false
,如下:if (absent && !hasOwn(prop, 'default')) { value = false }
-
如果父組件傳入了該
prop
屬性,那么需要滿足以下幾點:- 該屬性值為空字符串或者屬性值與屬性名相等;
-
prop
的type
屬性中不存在String
類型; - 如果
prop
的type
屬性中存在String
類型,那么Boolean
類型在type
屬性中的索引必須小于String
類型的索引,即Boolean
類型的優(yōu)先級更高;
則將該屬性值設(shè)置為
true
,如下:if (value === '' || value === hyphenate(key)) { const stringIndex = getTypeIndex(String, prop.type) if (stringIndex < 0 || booleanIndex < stringIndex) { value = true } }
另外,在判斷屬性值與屬性名相等的時候,是先將屬性名由駝峰式轉(zhuǎn)換成用
-
連接的字符串,下面的這幾種寫法,子組件的prop
都將被設(shè)置為true
:<Child name></Child> <Child name="name"></Child> <Child userName="user-name"></Child>
如果不是布爾類型,是其它類型的話,那就只需判斷父組件是否傳入該屬性即可,如果沒有傳入,則該屬性值為
undefined
,此時調(diào)用getPropDefaultValue
函數(shù)(關(guān)于該函數(shù)下面會介紹)獲取該屬性的默認值,并將其轉(zhuǎn)換成響應(yīng)式,如下:if (value === undefined) { value = getPropDefaultValue(vm, prop, key) // since the default value is a fresh copy, // make sure to observe it. const prevShouldObserve = shouldObserve toggleObserving(true) observe(value) toggleObserving(prevShouldObserve) }
如果父組件傳入了該屬性并且也有對應(yīng)的真實值,那么在非生產(chǎn)環(huán)境下會調(diào)用
assertProp
函數(shù)(關(guān)于該函數(shù)下面會介紹)校驗該屬性值是否與要求的類型相匹配。如下:if (process.env.NODE_ENV !== 'production' ) { assertProp(prop, key, value, vm, absent) }
最后將父組件傳入的該屬性的真實值返回。
3.4 getPropDefaultValue函數(shù)分析
getPropDefaultValue
函數(shù)的定義位于源碼的src/core/util/props.js
中,如下:
function getPropDefaultValue (vm, prop, key){
// no default, return undefined
if (!hasOwn(prop, 'default')) {
return undefined
}
const def = prop.default
// warn against non-factory defaults for Object & Array
if (process.env.NODE_ENV !== 'production' && isObject(def)) {
warn(
'Invalid default value for prop "' + key + '": ' +
'Props with type Object/Array must use a factory function ' +
'to return the default value.',
vm
)
}
// the raw prop value was also undefined from previous render,
// return previous default value to avoid unnecessary watcher trigger
if (vm && vm.$options.propsData &&
vm.$options.propsData[key] === undefined &&
vm._props[key] !== undefined
) {
return vm._props[key]
}
// call factory function for non-Function types
// a value is Function if its prototype is function even across different execution context
return typeof def === 'function' && getType(prop.type) !== 'Function'
? def.call(vm)
: def
}
該函數(shù)接收三個參數(shù),分別是:
- vm:當前實例;
- prop:子組件
props
選項中的每個key
對應(yīng)的值; - key:子組件
props
選項中的每個key
;
其作用是根據(jù)子組件props
選項中的key
獲取其對應(yīng)的默認值。
首先判斷prop
中是否有default
屬性,如果沒有,則表示沒有默認值,直接返回。如下:
if (!hasOwn(prop, 'default')) {
return undefined
}
如果有則取出default
屬性,賦給變量def
。接著判斷在非生產(chǎn)環(huán)境下def
是否是一個對象,如果是,則拋出警告:對象或數(shù)組默認值必須從一個工廠函數(shù)獲取。如下:
const def = prop.default
// warn against non-factory defaults for Object & Array
if (process.env.NODE_ENV !== 'production' && isObject(def)) {
warn(
'Invalid default value for prop "' + key + '": ' +
'Props with type Object/Array must use a factory function ' +
'to return the default value.',
vm
)
}
接著,再判斷如果父組件沒有傳入該props
屬性,但是在vm._props
中有該屬性值,這說明vm._props
中的該屬性值就是默認值,如下:
if (vm && vm.$options.propsData &&
vm.$options.propsData[key] === undefined &&
vm._props[key] !== undefined
) {
return vm._props[key]
}
最后,判斷def
是否為函數(shù)并且prop.type
不為Function
,如果是的話表明def
是一個返回對象或數(shù)組的工廠函數(shù),那么將函數(shù)的返回值作為默認值返回;如果def
不是函數(shù),那么則將def
作為默認值返回。如下:
return typeof def === 'function' && getType(prop.type) !== 'Function'
? def.call(vm)
: def
3.5 assertProp函數(shù)分析
assertProp
函數(shù)的定義位于源碼的src/core/util/props.js
中,如下:
function assertProp (prop,name,value,vm,absent) {
if (prop.required && absent) {
warn(
'Missing required prop: "' + name + '"',
vm
)
return
}
if (value == null && !prop.required) {
return
}
let type = prop.type
let valid = !type || type === true
const expectedTypes = []
if (type) {
if (!Array.isArray(type)) {
type = [type]
}
for (let i = 0; i < type.length && !valid; i++) {
const assertedType = assertType(value, type[i])
expectedTypes.push(assertedType.expectedType || '')
valid = assertedType.valid
}
}
if (!valid) {
warn(
`Invalid prop: type check failed for prop "${name}".` +
` Expected ${expectedTypes.map(capitalize).join(', ')}` +
`, got ${toRawType(value)}.`,
vm
)
return
}
const validator = prop.validator
if (validator) {
if (!validator(value)) {
warn(
'Invalid prop: custom validator check failed for prop "' + name + '".',
vm
)
}
}
}
該函數(shù)接收5個參數(shù),分別是:
- prop:
prop
選項; - name:
props
中prop
選項的key
; - value:父組件傳入的
propsData
中key
對應(yīng)的真實數(shù)據(jù); - vm:當前實例;
- absent:當前
key
是否在propsData
中存在,即父組件是否傳入了該屬性。
其作用是校驗父組件傳來的真實值是否與prop
的type
類型相匹配,如果不匹配則在非生產(chǎn)環(huán)境下拋出警告。
函數(shù)內(nèi)部首先判斷prop
中如果設(shè)置了必填項(即prop.required
為true
)并且父組件又沒有傳入該屬性,此時則拋出警告:提示該項必填。如下:
if (prop.required && absent) {
warn(
'Missing required prop: "' + name + '"',
vm
)
return
}
接著判斷如果該項不是必填的并且該項的值value
不存在,那么此時是合法的,直接返回。如下:
if (value == null && !prop.required) {
return
}
接下來定義了3個變量,分別是:
let type = prop.type
let valid = !type || type === true
const expectedTypes = []
- type:
prop
中的type
類型; - valid:校驗是否成功;
- expectedTypes:保存期望類型的數(shù)組,當校驗失敗拋出警告時,會提示用戶該屬性所期望的類型是什么;
通常情況下,type
可以是一個原生構(gòu)造函數(shù),也可以是一個包含多種類型的數(shù)組,還可以不設(shè)置該屬性。如果用戶設(shè)置的是原生構(gòu)造函數(shù)或數(shù)組,那么此時vaild
默認為false
(!type
),如果用戶沒有設(shè)置該屬性,表示不需要校驗,那么此時vaild
默認為true
,即校驗成功。
另外,當type
等于true
時,即出現(xiàn)這樣的寫法:props:{name:true}
,這說明prop
一定會校驗成功。所以當出現(xiàn)這種語法的時候,此時type === true
,所以vaild
默認為true
。
接下來開始校驗類型,如果用戶設(shè)置了type
屬性,則判斷該屬性是不是數(shù)組,如果不是,則統(tǒng)一轉(zhuǎn)化為數(shù)組,方便后續(xù)處理,如下:
if (type) {
if (!Array.isArray(type)) {
type = [type]
}
}
接下來遍歷type
數(shù)組,并調(diào)用assertType
函數(shù)校驗value
。assertType
函數(shù)校驗后會返回一個對象,如下:
{
vaild:true, // 表示是否校驗成功
expectedType:'Boolean' // 表示被校驗的類型
}
然后將被校驗的類型添加到expectedTypes
中,并將vaild
變量設(shè)置為assertedType.valid
,如下:
for (let i = 0; i < type.length && !valid; i++) {
const assertedType = assertType(value, type[i])
expectedTypes.push(assertedType.expectedType || '')
valid = assertedType.valid
}
這里請注意:上面循環(huán)中的條件語句有這樣一個條件:!vaild
,即type
數(shù)組中還要有一個校驗成功,循環(huán)立即結(jié)束,表示校驗通過。
接下來,如果循環(huán)完畢后vaild
為false
,即表示校驗未通過,則拋出警告。如下:
if (!valid) {
warn(
`Invalid prop: type check failed for prop "${name}".` +
` Expected ${expectedTypes.map(capitalize).join(', ')}` +
`, got ${toRawType(value)}.`,
vm
)
return
}
另外,prop
選項還支持自定義校驗函數(shù),如下:
props:{
// 自定義驗證函數(shù)
propF: {
validator: function (value) {
// 這個值必須匹配下列字符串中的一個
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
所以還需要使用用戶傳入的自定義校驗函數(shù)來校驗數(shù)據(jù)。首先獲取到用戶傳入的校驗函數(shù),調(diào)用該函數(shù)并將待校驗的數(shù)據(jù)傳入,如果校驗失敗,則拋出警告。如下:
const validator = prop.validator
if (validator) {
if (!validator(value)) {
warn(
'Invalid prop: custom validator check failed for prop "' + name + '".',
vm
)
}
}
4. 初始化methods
初始化methods
相較而言就比較簡單了,它的初始化函數(shù)定義位于源碼的src/core/instance/state.js
中,如下:
function initMethods (vm, methods) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
if (methods[key] == null) {
warn(
`Method "${key}" has an undefined value in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
}
}
從代碼中可以看到,初始化methods
無非就干了三件事:判斷method
有沒有?method
的命名符不符合命名規(guī)范?如果method
既有又符合規(guī)范那就把它掛載到vm
實例上。下面我們就逐行分析源碼,來過一遍這三件事。
首先,遍歷methods
選項中的每一個對象,在非生產(chǎn)環(huán)境下判斷如果methods
中某個方法只有key
而沒有value
,即只有方法名沒有方法體時,拋出異常:提示用戶方法未定義。如下:
if (methods[key] == null) {
warn(
`Method "${key}" has an undefined value in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
接著判斷如果methods
中某個方法名與props
中某個屬性名重復(fù)了,就拋出異常:提示用戶方法名重復(fù)了。如下:
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
接著判斷如果methods
中某個方法名如果在實例vm
中已經(jīng)存在并且方法名是以_
或$
開頭的,就拋出異常:提示用戶方法名命名不規(guī)范。如下:
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
其中,isReserved
函數(shù)是用來判斷字符串是否以_
或$
開頭。
最后,如果上述判斷都沒問題,那就method
綁定到實例vm
上,這樣,我們就可以通過this.xxx
來訪問methods
選項中的xxx
方法了,如下:
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
5. 初始化data
初始化data
也比較簡單,它的初始化函數(shù)定義位于源碼的src/core/instance/state.js
中,如下:
function initData (vm) {
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 */)
}
可以看到,initData
函數(shù)的邏輯并不復(fù)雜,跟initMethods
函數(shù)的邏輯有幾分相似。就是通過一系列條件判斷用戶傳入的data
選項是否合法,最后將data
轉(zhuǎn)換成響應(yīng)式并綁定到實例vm
上。下面我們就來仔細看一下代碼邏輯。
首先獲取到用戶傳入的data
選項,賦給變量data
,同時將變量data
作為指針指向vm._data
,然后判斷data
是不是一個函數(shù),如果是就調(diào)用getData
函數(shù)獲取其返回值,將其保存到vm._data
中。如果不是,就將其本身保存到vm._data
中。如下:
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
我們知道,無論傳入的data
選項是不是一個函數(shù),它最終的值都應(yīng)該是一個對象,如果不是對象的話,就拋出警告:提示用戶data
應(yīng)該是一個對象。如下:
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
)
}
接下來遍歷data
對象中的每一項,在非生產(chǎn)環(huán)境下判斷data
對象中是否存在某一項的key
與methods
中某個屬性名重復(fù),如果存在重復(fù),就拋出警告:提示用戶屬性名重復(fù)。如下:
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
接著再判斷是否存在某一項的key
與prop
中某個屬性名重復(fù),如果存在重復(fù),就拋出警告:提示用戶屬性名重復(fù)。如下:
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
)
}
如果都沒有重復(fù),則調(diào)用proxy
函數(shù)將data
對象中key
不以_
或$
開頭的屬性代理到實例vm
上,這樣,我們就可以通過this.xxx
來訪問data
選項中的xxx
數(shù)據(jù)了。如下:
if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
最后,調(diào)用observe
函數(shù)將data
中的數(shù)據(jù)轉(zhuǎn)化成響應(yīng)式,如下:
observe(data, true /* asRootData */)
6. 初始化computed
計算屬性computed
相信大家一定不會陌生,在日常開發(fā)中肯定會經(jīng)常用到,而且我們知道計算屬性有一個很大的特點就是: 計算屬性的結(jié)果會被緩存,除非依賴的響應(yīng)式屬性變化才會重新計算。 那么接下來我們就來看一下計算屬性是如何實現(xiàn)這些功能的的。
6.1 回顧用法
首先,根據(jù)官方文檔的使用示例,我們來回顧一下計算屬性的用法,如下:
var vm = new Vue({
data: { a: 1 },
computed: {
// 僅讀取
aDouble: function () {
return this.a * 2
},
// 讀取和設(shè)置
aPlus: {
get: function () {
return this.a + 1
},
set: function (v) {
this.a = v - 1
}
}
}
})
vm.aPlus // => 2
vm.aPlus = 3
vm.a // => 2
vm.aDouble // => 4
可以看到,computed
選項中的屬性值可以是一個函數(shù),那么該函數(shù)默認為取值器getter
,用于僅讀取數(shù)據(jù);還可以是一個對象,對象里面有取值器getter
和存值器setter
,用于讀取和設(shè)置數(shù)據(jù)。
6.2 initComputed函數(shù)分析
了解了計算屬性的用法之后,下面我們就來分析一下計算屬性的初始化函數(shù)initComputed
的內(nèi)部原理是怎樣的。initComputed
函數(shù)的定義位于源碼的src/core/instance/state.js
中,如下:
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
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
)
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
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)
}
}
}
}
可以看到,在函數(shù)內(nèi)部,首先定義了一個變量watchers
并將其賦值為空對象,同時將其作為指針指向vm._computedWatchers
,如下:
const watchers = vm._computedWatchers = Object.create(null)
接著,遍歷computed
選項中的每一項屬性,首先獲取到每一項的屬性值,記作userDef
,然后判斷userDef
是不是一個函數(shù),如果是函數(shù),則該函數(shù)默認為取值器getter
,將其賦值給變量getter
;如果不是函數(shù),則說明是一個對象,則取對象中的get
屬性作為取值器賦給變量getter
。如下:
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
接著判斷在非生產(chǎn)環(huán)境下如果上面兩種情況取到的取值器不存在,則拋出警告:提示用戶計算屬性必須有取值器。如下:
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
接著判斷如果不是在服務(wù)端渲染環(huán)境下,則創(chuàng)建一個watcher
實例,并將當前循環(huán)到的的屬性名作為鍵,創(chuàng)建的watcher
實例作為值存入watchers
對象中。如下:
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
最后,判斷當前循環(huán)到的的屬性名是否存在于當前實例vm
上,如果存在,則在非生產(chǎn)環(huán)境下拋出警告;如果不存在,則調(diào)用defineComputed
函數(shù)為實例vm
上設(shè)置計算屬性。
以上就是initComputed
函數(shù)的內(nèi)部邏輯,接下里我們再來看一下defineComputed
函數(shù)是如何為實例vm
上設(shè)置計算屬性的。
6.3 defineComputed函數(shù)分析
defineComputed
函數(shù)的定義位于源碼的src/core/instance/state.js
中,如下:
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function defineComputed (target,key,userDef) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
sharedPropertyDefinition.set = userDef.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
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
該函數(shù)接受3個參數(shù),分別是:target
、key
和userDef
。其作用是為target
上定義一個屬性key
,并且屬性key
的getter
和setter
根據(jù)userDef
的值來設(shè)置。下面我們就來看一下該函數(shù)的具體邏輯。
首先定義了變量sharedPropertyDefinition
,它是一個默認的屬性描述符。
接著,在函數(shù)內(nèi)部定義了變量shouldCache
,用于標識計算屬性是否應(yīng)該有緩存。該變量的值是當前環(huán)境是否為非服務(wù)端渲染環(huán)境,如果是非服務(wù)端渲染環(huán)境則該變量為true
。也就是說,只有在非服務(wù)端渲染環(huán)境下計算屬性才應(yīng)該有緩存。如下:
const shouldCache = !isServerRendering()
接著,判斷如果userDef
是一個函數(shù),則該函數(shù)默認為取值器getter
,此處在非服務(wù)端渲染環(huán)境下并沒有直接使用userDef
作為getter
,而是調(diào)用createComputedGetter
函數(shù)(關(guān)于該函數(shù)下面會介紹)創(chuàng)建了一個getter
,這是因為userDef
只是一個普通的getter
,它并沒有緩存功能,所以我們需要額外創(chuàng)建一個具有緩存功能的getter
,而在服務(wù)端渲染環(huán)境下可以直接使用userDef
作為getter
,因為在服務(wù)端渲染環(huán)境下計算屬性不需要緩存。由于用戶沒有設(shè)置setter
函數(shù),所以將sharedPropertyDefinition.set
設(shè)置為noop
。如下:
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
sharedPropertyDefinition.set = noop
}
如果userDef
不是一個函數(shù),那么就將它當作對象處理。在設(shè)置sharedPropertyDefinition.get
的時候先判斷userDef.get
是否存在,如果不存在,則將其設(shè)置為noop
,如果存在,則同上面一樣,在非服務(wù)端渲染環(huán)境下并且用戶沒有明確的將userDef.cache
設(shè)置為false
時調(diào)用createComputedGetter
函數(shù)創(chuàng)建一個getter
賦給sharedPropertyDefinition.get
。然后設(shè)置sharedPropertyDefinition.set
為userDef.set
函數(shù)。如下:
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
sharedPropertyDefinition.set = userDef.set
? userDef.set
: noop
接著,再判斷在非生產(chǎn)環(huán)境下如果用戶沒有設(shè)置setter
的話,那么就給setter
一個默認函數(shù),這是為了防止用戶在沒有設(shè)置setter
的情況下修改計算屬性,從而為其拋出警告,如下:
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
)
}
}
最后調(diào)用Object.defineProperty
方法將屬性key
綁定到target
上,其中的屬性描述符就是上面設(shè)置的sharedPropertyDefinition
。如此以來,就將計算屬性綁定到實例vm
上了。
以上就是defineComputed
函數(shù)的所有邏輯。另外,我們發(fā)現(xiàn),計算屬性有沒有緩存及其響應(yīng)式貌似主要在于是否將getter
設(shè)置為createComputedGetter
函數(shù)的返回結(jié)果。那么接下來,我們就對這個createComputedGetter
函數(shù)一探究竟。
6.4 createComputedGetter函數(shù)分析
createComputedGetter
函數(shù)的定義位于源碼的src/core/instance/state.js
中,如下:
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
watcher.depend()
return watcher.evaluate()
}
}
}
可以看到,該函數(shù)是一個高階函數(shù),其內(nèi)部返回了一個computedGetter
函數(shù),所以其實是將computedGetter
函數(shù)賦給了sharedPropertyDefinition.get
。當獲取計算屬性的值時會執(zhí)行屬性的getter
,而屬性的getter
就是 sharedPropertyDefinition.get
,也就是說最終執(zhí)行的 computedGetter
函數(shù)。
在computedGetter
函數(shù)內(nèi)部,首先存儲在當前實例上_computedWatchers
屬性中key
所對應(yīng)的watcher
實例,如果watcher
存在,則調(diào)用watcher
實例上的depend
方法和evaluate
方法,并且將evaluate
方法的返回值作為計算屬性的計算結(jié)果返回。那么watcher
實例上的depend
方法和evaluate
方法又是什么呢?
6.5 depend和evaluate
回顧上文中創(chuàng)建watcher
實例的時候:
const computedWatcherOptions = { computed: true }
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
傳入的參數(shù)中第二個參數(shù)是getter
函數(shù),第四個參數(shù)是一個對象computedWatcherOptions
。
我們再回顧Watcher
類的定義,如下:
export default class Watcher {
constructor (vm,expOrFn,cb,options,isRenderWatcher) {
if (options) {
// ...
this.computed = !!options.computed
// ...
} else {
// ...
}
this.dirty = this.computed // for computed watchers
if (typeof expOrFn === 'function') {
this.getter = expOrFn
}
if (this.computed) {
this.value = undefined
this.dep = new Dep()
}
}
evaluate () {
if (this.dirty) {
this.value = this.get()
this.dirty = false
}
return this.value
}
/**
* Depend on this watcher. Only for computed property watchers.
*/
depend () {
if (this.dep && Dep.target) {
this.dep.depend()
}
}
update () {
if (this.computed) {
if (this.dep.subs.length === 0) {
this.dirty = true
} else {
this.getAndInvoke(() => {
this.dep.notify()
})
}
}
}
getAndInvoke (cb: Function) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
this.dirty = false
if (this.user) {
try {
cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
cb.call(this.vm, value, oldValue)
}
}
}
}
可以看到,在實例化Watcher
類的時候,第四個參數(shù)傳入了一個對象computedWatcherOptions = { computed: true }
,該對象中的computed
屬性標志著這個watcher
實例是計算屬性的watcher
實例,即Watcher
類中的this.computed
屬性,同時類中還定義了this.dirty
屬性用于標志計算屬性的返回值是否有變化,計算屬性的緩存就是通過這個屬性來判斷的,每當計算屬性依賴的數(shù)據(jù)發(fā)生變化時,會將this.dirty
屬性設(shè)置為true
,這樣下一次讀取計算屬性時,會重新計算結(jié)果返回,否則直接返回之前的計算結(jié)果。
當調(diào)用watcher.depend()
方法時,會將讀取計算屬性的那個watcher
添加到計算屬性的watcher
實例的依賴列表中,當計算屬性中用到的數(shù)據(jù)發(fā)生變化時,計算屬性的watcher
實例就會執(zhí)行watcher.update()
方法,在update
方法中會判斷當前的watcher
是不是計算屬性的watcher
,如果是則調(diào)用getAndInvoke
去對比計算屬性的返回值是否發(fā)生了變化,如果真的發(fā)生變化,則執(zhí)行回調(diào),通知那些讀取計算屬性的watcher
重新執(zhí)行渲染邏輯。
當調(diào)用watcher.evaluate()
方法時,會先判斷this.dirty
是否為true
,如果為true
,則表明計算屬性所依賴的數(shù)據(jù)發(fā)生了變化,則調(diào)用this.get()
重新獲取計算結(jié)果最后返回;如果為false
,則直接返回之前的計算結(jié)果。
其內(nèi)部原理如圖所示:
7. 初始化watch
接下來就是最后一個初始化函數(shù)了——初始化watch
選項。在日常開發(fā)中watch
選項也經(jīng)常會使用到,它可以用來偵聽某個已有的數(shù)據(jù),當該數(shù)據(jù)發(fā)生變化時執(zhí)行對應(yīng)的回調(diào)函數(shù)。那么,接下來我們就來看一些watch
選項是如何被初始化的。
7.1 回顧用法
首先,根據(jù)官方文檔的使用示例,我們來回顧一下watch
選項的用法,如下:
var vm = new Vue({
data: {
a: 1,
b: 2,
c: 3,
d: 4,
e: {
f: {
g: 5
}
}
},
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
// methods選項中的方法名
b: 'someMethod',
// 深度偵聽,該回調(diào)會在任何被偵聽的對象的 property 改變時被調(diào)用,不論其被嵌套多深
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
},
// 該回調(diào)將會在偵聽開始之后被立即調(diào)用
d: {
handler: 'someMethod',
immediate: true
},
// 調(diào)用多個回調(diào)
e: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
}
],
// 偵聽表達式
'e.f': function (val, oldVal) { /* ... */ }
}
})
vm.a = 2 // => new: 2, old: 1
可以看到,watch
選項的用法非常靈活。首先watch
選項是一個對象,鍵是需要觀察的表達式,值是對應(yīng)回調(diào)函數(shù)。值也可以是方法名,或者包含選項的對象。既然給用戶提供的用法靈活,那么在代碼中就需要按條件來判斷,根據(jù)不同的用法做相應(yīng)的處理。
7.2 initWatch函數(shù)分析
了解了watch
選項的用法之后,下面我們就來分析一下watch
選項的初始化函數(shù)initWatch
的內(nèi)部原理是怎樣的。initWatch
函數(shù)的定義位于源碼的src/core/instance/state.js
中,如下:
function initWatch (vm, watch) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
可以看到,在函數(shù)內(nèi)部會遍歷watch
選項,拿到每一項的key
和對應(yīng)的值handler
。然后判斷handler
是否為數(shù)組,如果是數(shù)組則循環(huán)該數(shù)組并將數(shù)組中的每一項依次調(diào)用createWatcher
函數(shù)來創(chuàng)建watcher
;如果不是數(shù)組,則直接調(diào)用createWatcher
函數(shù)來創(chuàng)建watcher
。那么這個createWatcher
函數(shù)是如何創(chuàng)建watcher
的呢?
7.3 createWatcher函數(shù)分析
createWatcher
函數(shù)的定義位于源碼的src/core/instance/state.js
中,如下:
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
可以看到,該函數(shù)接收4個參數(shù),分別是:
- vm:當前實例;
- expOrFn:被偵聽的屬性表達式
- handler:
watch
選項中每一項的值 - options:用于傳遞給
vm.$watch
的選項對象
在該函數(shù)內(nèi)部,首先會判斷傳入的handler
是否為一個對象,如果是一個對象,那么就認為用戶使用的是這種寫法:
watch: {
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
}
}
即帶有偵聽選項的寫法,此時就將handler
對象整體記作options
,把handler
對象中的handler
屬性作為真正的回調(diào)函數(shù)記作handler
,如下:
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
接著判斷傳入的handler
是否為一個字符串,如果是一個字符串,那么就認為用戶使用的是這種寫法:
watch: {
// methods選項中的方法名
b: 'someMethod',
}
即回調(diào)函數(shù)是methods
選項中的一個方法名,我們知道,在初始化methods
選項的時候會將選項中的每一個方法都綁定到當前實例上,所以此時我們只需從當前實例上取出該方法作為真正的回調(diào)函數(shù)記作handler
,如下:
if (typeof handler === 'string') {
handler = vm[handler]
}
如果既不是對象又不是字符串,那么我們就認為它是一個函數(shù),就不做任何處理。
針對不同類型的值處理完畢后,expOrFn
是被偵聽的屬性表達式,handler
變量是回調(diào)函數(shù),options
變量為偵聽選項,最后,調(diào)用vm.$watcher
方法(關(guān)于該方法在介紹全局實例方法的時候會詳細介紹)并傳入以上三個參數(shù)完成初始化watch
。
8. 總結(jié)
本篇文章介紹了生命周期初始化階段所調(diào)用的第五個初始化函數(shù)——initState
。該初始化函數(shù)內(nèi)部總共初始化了5個選項,分別是:props
、methods
、data
、computed
和watch
。
這5個選項的初始化順序不是任意的,而是經(jīng)過精心安排的。只有按照這種順序初始化我們才能在開發(fā)中在data
中可以使用props
,在watch
中可以觀察data
和props
。
這5個選項中的所有屬性最終都會被綁定到實例上,這也就是我們?yōu)槭裁纯梢允褂?code>this.xxx來訪問任意屬性。同時正是因為這一點,這5個選項中的所有屬性名都不應(yīng)該有所重復(fù),這樣會造成屬性之間相互覆蓋。文章來源:http://www.zghlxwxcb.cn/news/detail-706166.html
最后,我們對這5個選項分別都是如何進行初始化的內(nèi)部原理進行了逐一分析。文章來源地址http://www.zghlxwxcb.cn/news/detail-706166.html
到了這里,關(guān)于【Vue2.0源碼學(xué)習(xí)】生命周期篇-初始化階段(initState)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!