前言
在前面我們分析到了new Vue的過(guò)程倍试,明白了dom節(jié)點(diǎn)是如何被創(chuàng)建的震贵。下面我們來(lái)說(shuō)說(shuō)vue中比較重要的部分,響應(yīng)對(duì)象得生成。
initProps, initData入口
// 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.
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 (!isRoot && !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)
}
// initData重要的代碼
function initData (vm: Component) {
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 */)
}
解釋
- initProps中遍歷propsOptions冕碟,讓propsOptions中的每一個(gè)值執(zhí)行defineReactive,這個(gè)defineReactive函數(shù)就是調(diào)用object.defineProperty,將props上的數(shù)據(jù)變成響應(yīng)式。
- initData中最后一句代碼匆浙,將data中的數(shù)據(jù)進(jìn)行監(jiān)聽(tīng)安寺,主要是執(zhí)行observe(data, true),observe又是調(diào)用def函數(shù)將data上的數(shù)據(jù)變成響應(yīng)式
我們接下來(lái)看看observe這個(gè)函數(shù)
observe函數(shù)解析
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
解釋
- observe是一個(gè)類首尼,定義了一個(gè)Dep實(shí)例進(jìn)行依賴收集很重要挑庶,然后調(diào)用了def對(duì)data上的數(shù)據(jù)綁定ob屬性,值是observe實(shí)例
- 如果value是對(duì)象調(diào)用walk,這個(gè)函數(shù)就是遍歷對(duì)象软能,然會(huì)再次調(diào)用defineReactive,將對(duì)象的每一個(gè)值進(jìn)行響應(yīng)式設(shè)置迎捺。
- 如果value是一個(gè)數(shù)組,調(diào)用observeArray將數(shù)組的每一項(xiàng)再次進(jìn)行遞歸的observe查排。
- 由此可知重要的函數(shù)是這個(gè)defineReactive凳枝,下面我們來(lái)看看defineReactive到底做了什么
defineReactive的內(nèi)部實(shí)現(xiàn)
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
解釋
- 首先獲取一個(gè)Dep類做一個(gè)依賴收集很重要,然后獲取對(duì)象的每一個(gè)屬性的get set,如果屬性值是一個(gè)對(duì)象再次調(diào)用observe進(jìn)行屬性的設(shè)置
- 關(guān)鍵的點(diǎn)是調(diào)用Object.defineProperty對(duì)傳入的屬性進(jìn)行響應(yīng)式設(shè)置跋核,比較關(guān)鍵的點(diǎn)是get函數(shù)里面Dep.depen()和childOb.dep.depend()還有dependArray(value)這些就是在做依賴收集岖瑰,至于怎么收集的我們后面去講解
- 我們了解到了響應(yīng)式對(duì)象創(chuàng)建的過(guò)程對(duì)象調(diào)用observe-------再次調(diào)用defineReactive------最后調(diào)用Object.defineProperty依賴收集就是在get中進(jìn)行的。
總結(jié)
這篇文章主要是通過(guò)initPropps和initData來(lái)說(shuō)明怎么創(chuàng)建的響應(yīng)式對(duì)象砂代,其實(shí)initComputed也是一樣的蹋订,下篇文章就來(lái)介紹下vue中依賴收集是怎么做的。