前提:已經clone下來vue原始碼
vue的data屬性是在實際使用中最常見的了,以及大家老生常談的雙向繫結。本篇文章介紹了data屬性的初始化以及雙向繫結中model繫結部分。
先概述一下整體的邏輯過程:
1、初始化vue例項
2、在例項created生命週期之前執行initState函式,這個函式對props、methods、data、computed、watch初始化
3、在data初始化中,vue會對data繫結一個觀察者Observer,每次data中的欄位更新後都會通知依賴收集器Dep觸發響應式更新
接下來說正題:data是在new Vue中初始化的,看一下具體路徑
1、new Vue初始化位置:/src/core/instance/index.js中的initMixin函式中
2、initMixin函式在/src/core/instance/state.js中呼叫了同檔案中的initState函式
3、在initState函式中首先判斷了使用者是否已經配置了data屬性,如下圖
4、我們直接看一下initData函式
在我們使用vue的時候data可以有兩種方式定義,一種是用函式返回一個物件,另一種是直接定義一個物件。
1、在initData函式中,首先判斷了data型別是否為函式,如果為函式的話則呼叫getData函式只是.call一下函式返回物件,將物件首先複製到例項的_data上。
2、這裡isPlainObject函式是通過呼叫data物件的toString函式判斷是否為Object,它判斷了_toString.call(obj) === `[object Object]`
3、keys是data中的所有欄位、props是例項中的props、methods是例項中的methodes,while開始對data中每個key進行遍歷,首先判斷methods中是否有重名的key,然後再判斷props中是否有重名的key。這裡還會通過isReserved函式判斷是用來判斷是否用到了保留關鍵字元“_”和“$”
4、在key字串中不含有關鍵字元,接下來會呼叫proxy函式,這個函式的工作就是設定好key的get、set屬性並配置到vue例項上。這裡我們看到proxy中vue.key的get、set方法實際獲取、改變的都是vue._data.key。
雙向繫結之Observer
接下來就是重頭戲了,vue雙向繫結中其中之一model繫結,接上文的原始碼最後一行回撥用observe函式開始model繫結。在開始繼續上文之前我們先看一下Observe都做了什麼
先看看人家官方解釋:觀察者類附加到每個觀察物件。 一旦連線,觀察者將目標物件的屬性鍵轉換為收集依賴關係和分派更新的getter / setter。說一下這個Observer類:
1、constructor中初始化了value(datakey)、dep(依賴收集器)、vmCount(訂閱此觀察者的節點)
2、接下來呼叫了def函式,為data定義__ob__屬性,可以理解為將data繫結了一個observer,__ob__指向了這個oberver
3、接下來會判斷value是否為陣列,我們先看一下value為物件的情況,它會呼叫this.walk函式:walk函式先獲取物件的所有key,然後遍歷key並執行defineReactive函式。
我們來看看defineReactive函式都幹嘛了(一張圖截不下就直接上原始碼吧)
/**
* Define a reactive property on an Object.
*/
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()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
複製程式碼
面試中、各種文章中說的雙向繫結的get、set就在這個函式中實現了,讓我們來解析一下吧
1、new了一個dep依賴收集(後面文章會提起)
2、通過getOwnPropertyDescriptor獲取到data中key對應的訪問器屬性賦值給property,並判斷當前key是否在data中並且是可配置的(configurable為true才可以)
3、獲取訪問器屬性中的get、set屬性,判斷存在setter或不存在getter以及實參各戶只有兩個時將val為data中key對應的value(在walk中就是傳了兩個引數)
4、這裡我們看到了shallow欄位,這裡我理解為對val的深度淺度遍歷(如果為物件的話)繫結一個觀察者。那麼由於walk函式中並沒有傳則預設!shallow為ture。observe後面我們在討論
5、接下來就到了萬人皆知的get、set了,先說get屬性,我看到了它會先判斷是否已經設定get屬性(第3條中獲取),如果有則call getter函式,如果沒有則直接返回val值。接下來會判斷Dep的target(它的作用後面文章會搭配watcher詳細說),這裡先理解為每次get的時候都會訂閱到Dep的subs中,包括子元素、如果為陣列的話則陣列中每個子元素都會訂閱到Dep的subs中。
6、再看看set屬性,首先通過getter來獲取到value,然後判斷如果新的value與舊值===全等、新value與舊value的getter方法被重寫過(每次get的value不一致)則不做任何事情,這也就是當我們this.sthKey = oldValue重複賦值相同,不會觸發響應式的原因。
7、接下來判斷了一下是否有使用者自定義的setter,如果有則setter.call(),如果沒有的話則是常規賦值操作。
8、這裡我們看到它再次對新的value進行了一次深度遍歷為每個子元素註冊一個觀察者。
9、最後就是dep.notify()了,這個函式就是在data中的資料set之後觀察者告訴訂閱者要更新了完成model to view的響應式操作。
10、到這裡就將data中每個key的get、set屬性進行重寫結束了,雙向繫結也完成了一半。
11、我們回頭看一下如果是陣列的話,vue是如何做響應式的呢?首先判斷了hasProto這個是一個布林型它返回了__proto__是否在物件中
這裡augment會根據hasProto賦值上面兩個不同的函式,protoAugemnt很簡單就是將value的__proto__賦值arrayMethods(arrayMethods為Array.prototype),copyAugment則遍歷arrayKeys(arrayKeys為arrayMethods的OwnPropertyNames)每一個key,呼叫def函式,為data定義key屬性(也就是定義Array.prototype)。
接下來就是遍歷整個陣列,對每個子元素呼叫observe函式。
我們來看看observe函式的實現。
1、首先函式判斷了value必須為物件且不為VNode節點
2、初始化一個Observer物件,判斷value是否含有__ob__屬性且該屬性是Observer物件,如果均滿足則將初始化的Observer物件指向了value的__ob__屬性,其次判斷shouldObserve(常量預設為true)、是否為服務端渲染、value為陣列或為物件、value物件是否可擴充套件(isExtensible es6語法)、是否為vue例項,經過以上的判斷後ob又指向了新Observer物件(由value初始化)
3、判斷當前的asRootData當前的value是否為根結點的data,以及ob物件存在,則ob訂閱此觀察者的節點加一,最終返回ob物件。
以上就是筆者的理解,接下來會盡量將vue的各個模組都遍歷到,如有任何建議歡迎提出。