最近一段時間在閱讀Vue源碼,從它的核心原理入手,開始了源碼的學習,而其核心原理就是其數據的響應式。而且結合設計模式進行學習html
這裏簡短的介紹這兩種模式的聯繫和差別,react
觀察者模式有一個別名叫「發佈-訂閱模式」,或者說是「訂閱-發佈模式」,訂閱者和訂閱目標是聯繫在一塊兒的,當訂閱目標發生改變時,逐個通知訂閱者。咱們能夠用報紙期刊的訂閱來形象的說明,當你訂閱了一份報紙,天天都會有一份最新的報紙送到你手上,有多少人訂閱報紙,報社就會發多少份報紙,報社和訂報紙的客戶就是上面文章開頭所說的「一對多」的依賴關係。git
可是通過時間的沉澱,彷佛他已經強大了起來,已經獨立於觀察者模式,成爲另一種不一樣的設計模式。github
在如今的發佈訂閱模式中,稱爲發佈者的消息發送者不會將消息直接發送給訂閱者,這意味着發佈者和訂閱者不知道彼此的存在。在發佈者和訂閱者之間存在第三個組件,稱爲調度中心或事件通道,它維持着發佈者和訂閱者之間的聯繫,過濾全部發布者傳入的消息並相應地分發它們給訂閱者。設計模式
舉一個例子,你在微博上關注了A,同時其餘不少人也關注了A,那麼當A發佈動態的時候,微博就會爲大家推送這條動態。A就是發佈者,你是訂閱者,微博就是調度中心,你和A是沒有直接的消息往來的,全是經過微博來協調的(你的關注,A的發佈動態)。數組
能夠看出,發佈訂閱模式相比觀察者模式多了個事件通道,事件通道做爲調度中心,管理事件的訂閱和發佈工做,完全隔絕了訂閱者和發佈者的依賴關係。即訂閱者在訂閱事件的時候,只關注事件自己,而不關心誰會發布這個事件;發佈者在發佈事件的時候,只關注事件自己,而不關心誰訂閱了這個事件。閉包
觀察者模式有兩個重要的角色,即目標和觀察者。在目標和觀察者之間是沒有事件通道的。一方面,觀察者要想訂閱目標事件,因爲沒有事件通道,所以必須將本身添加到目標(Subject) 中進行管理;另外一方面,目標在觸發事件的時候,也沒法將通知操做(notify) 委託給事件通道,所以只能親自去通知全部的觀察者。異步
當咱們在data
中定義一個值的時候,以下:函數
const vm = new Vue({
data() {
return {
message: ''
}
},
template: '<div>{{message}}</div>'
})
vm.message = 'hello';
複製代碼
此時Vue
內部發生了什麼,下面列出須要解決的問題以下:oop
data
中的值發生改變時,是如何更新視圖的data
值的時候,內部這個流程是如何的,結合講解相信你對響應式原理有更深刻的理解。爲了讓結構更加清晰,這裏只考慮一個視圖,而且不會有
computed
的狀況。 在講解原理以前,首先對幾個單詞進行定義:
Observer
首先看看當實例化Vue
的時候,對data
是如何進行處理的
_init
=> mount => this._watcher = new Watcher(vm, updateComponent, noop)
=> Dep.target = this._watcher
=> observe(data, true)
=> new Observer(data)
複製代碼
new Vue
會調用_init
函數mount
把須要渲染的模板掛載到元素上Watcher
實例Watcher
實例賦值給Dep.target
data
返回的數據進行observe
new Observer
遍歷data
進行setter
和getter
綁定下面來看看observe
函數的實現:
function observe(value, asRootData) {
let ob;
// 檢測當前數據是否被observe過,若是是則沒必要重複綁定
if(hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob;
}
複製代碼
首先調用的就是上面這個函數,__ob__
用戶判斷是否有Observer
實例,若是有就使用原來的,若是沒有就建立一個新的Observer
實例。vmCount
表示該Vue
實例使用的次數,asRootData
表示是不是data
的跟,例如在一個template
中一個相同的組件使用了兩次:
<div>
<my-component />
<my-component />
</div>
複製代碼
這個時候vmCount
就爲2
。接下來看Observer
的實現:
class Observer {
constructor(value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this)
if (Array.isArray(value)) {
// 若是是數組則須要遍歷數組的每一個成員進行observe
// 這裏會對數組原有的方法進行從新定義
this.observeArray(value)
} else {
// 若是對象則調用下面的程序
this.walk(value)
}
}
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
複製代碼
下圖是Observer
類的結構
data
中定義的值,而後在每一個遍歷的屬性下面添加
__ob__
,而後在
__ob__
定義
Dep
,根據數據類型的不一樣調用不一樣的方法,若是是數組則使用
observeArray
,該方法會重寫數數組的7種方法,對數組的每一個成員調用
observe
函數,若是是普通對象,則遍歷他的屬性調用
defineReactive
,進行
getter/setter
綁定。
defineReactive
是
Vue
最核心的內容,使用方法如:
defineReactive(obj, keys[i], obj[keys[i]])
。當在
data
中定義一個屬性的時候,當咱們更改該值的時候,視圖是如何知道,這個值發生了改變來更新視圖的。
function defineReactive(obj, key, val) {
// 在閉包中定義一個dep對象
const dep = new Dep();
// 對象的子對象遞歸進行observe並返回子節點的Observer對象
let childOb = 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就是將dep和watcher進行互相綁定
// Dep.target表示須要綁定的watcher
dep.depend();
if (childOb) {
// 子對象進行依賴收集,其實就是將同一個watcher觀察者實例放進兩個depend中
// 一個是正在自己閉包中的depend,另外一個是子元素的depend
childOb.dep.depend();
}
if (Array.isArray(value)) {
// 若是是數組,須要對數組的每一個成員都進行依賴收集
dependArray(value)
}
}
return value;
},
set: function reactiveSetter(newVal) {
// 經過getter方法獲取當前值,與新值發生比較,一致則不須要執行下面的操做
const value = getter ? getter.call(obj) : val;
if (newVal === value || (newVal !== newVal && value !== value)) {
return false;
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 新的值須要從新observe,保證數據響應式
childOb = observe(newVal)
// 通知全部觀察者
dep.notify()
}
})
}
複製代碼
經過Object.defineProperty
把數據進行了getter
和setter
綁定。getter
用於依賴收集,setter
用於經過dep
去通知watcher
, watcher
進行執行變化。 如何進行依賴收集的,能夠經過一個例子進行解釋:
data() {
return {
message: [1, 2]
}
}
複製代碼
結合一個流程圖進行分析上面例子:
observe(data)
=> data.__ob__ = new Observer(data)
=> walk(data)
=> childOb = observe(message)
=> message.__ob__ = new Observer(data)
=> message.__ob__.dep = new Dep;
=> childOb ? childOb.dep.depend();
複製代碼
分析其過程就是:
data
函數返回的對象添加__ob__
,返回具體的內容以下:const res = {
message: [1, 2]
__ob___: new Observer(data)
}
複製代碼
res
, 由於res
爲對象,因此執行walk
observe(message)
message
添加__ob__
,__ob__
上存在一個dep
用於依賴收集childOb = message.__ob__
,此時同一個watcher
放入子對象中,也就是message.__ob__.dep
中 回顧上面的分析,就可以區分出Observer
和defineReactive
中兩個dep
的區別了,這兩個地方都聲明瞭new Dep
,Observer
的dep
用於收集對象和數組的訂閱者,掛載在對象的屬性上。當對象或者數組增刪元素時調用$set
,獲取到__ob__
進行依賴收集,而後調用ob.dep.notify
j進行更新。在defineReactive
中,這個dep
是存在一個閉包中,這是對對象屬性服務的,在獲取屬性值的時候進行依賴收集,設置屬性值的時候發佈更新。Dep
下面來介紹一下dep
,源碼以下:
let uid = 0;
class Dep {
constructor() {
this.id = uid++;
this.subs = []
}
// 添加一個訂閱者
addSub(sub) {
this.subs.push(sub)
}
// 移除一個觀察者對象
removeSub(sub) {
remove(this.subs, sub)
}
// 依賴收集,當存在Dep.target的時候添加觀察者對象
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 通知全部訂閱者
notify() {
const subs = this.subs.slice();
for(let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}
複製代碼
結構以下:
get
的時候,先前
defineReactive
中
const dep = new Dep()
閉包中,就會把當前的
Watcher
訂閱者加入到
subs
中。
Dep
是發佈訂閱者模型中的發佈者,
Watcher
是訂閱者,一個
Dep
實例對應一個對象屬性或一個被觀察的對象,用於收集和數據改變時,發佈更新。好比說有這個一個
data
data() {
return {
message: 'a'
}
}
複製代碼
觸發視圖有兩種方法:
getter/setter
,從新設置message
的值,設置的過程當中會觸發dep.notify
進行發佈更新, 好比this.message = 'b'
$set
函數: this.$set(this.message, 'fpx', 'number-one')
,這會獲取到message
的__ob__
上的dep
進行發佈更新Watcher
Watcher
是一個訂閱者。依賴收集後watcher
會被存放在Dep
的subs
中,數據變更的時候經過dep
發佈者發佈信息,相關的訂閱者watcher
收到信息後經過cb
進行視圖更新。 Watcher
內容不少,咱們只關注最重要的一些部分:
class Watcher {
constructor(vm, expOrFn, cb, options) {
this.vm = vm;
// 存放訂閱者實例
vm._watchers.push(this)
this.deps = [];
this.newDeps = []
this.depsIds= new Set();
this.newDepIds new Set();
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.value = this.get();
}
get() {
pushTarget(this)
const vm = this.vm;
value = this.getter.call(vm, vm);
popTarget();
this.cleanupDeps();
return value
}
// 添加一個依賴關係到Deps集合中
addDep(dep) {
const id = dep.id;
if (!this.newDepsIds.has(id)) {
this.newDepsIds.add(id)
this.newDeps.push(dep);
// 這裏作一個去重,若是depIds裏包含這個id,那麼以前給depId添加這個id的時候
// 已經調用過dep.addSub(this),避免了重複添加
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
// 用於更新模板
update() {
if (this.sync) {
// 同步則執行run直接渲染視圖
this.run();
} else {
// 異步推送到觀察者隊列中,下一個tick時調用,最後會調用run方法
queueWatcher(this)
}
}
// 收集該watcher的全部deps原理
depend() {
let i = this.deps.length;
while(i--) {
this.deps[i].depend();
}
}
}
複製代碼
Watcher
結構以下:
Watcher
構造函數作的事情:
Dep.target = new Watcher(vm, updateComponent, noop = {})
=> 初始化變量
=> 獲取getter函數
=> 調用get函數,get函數會調用getter函數,從而收集依賴
複製代碼
在建立Vue
實例的時候,觸發getter
就會進行依賴收集,下面是這幾種狀況: Watcher
有四個使用的場景,只有在這四種場景中,Watcher
纔會收集依賴,更新模板或表達式
Vue
實例時watch
選項裏的數據computed
選型裏的數據所依賴的數據$watch
觀察的數據或者表達式 在前面代碼中聲明瞭Dep.target
,這個是幹嗎用的呢。在前面提到依賴收集的時機,是當咱們獲取元素屬性值的時候,可是此時不知道哪一個是正確的watcher
,因此定義一個全局變量記錄當前的Watcher
,方便添加當前正在執行的Watcher
。 Watcher
對象中有兩個屬性: deps
和newDeps
。他們用來記錄上一次Watcher
收集的依賴和新一輪Watcher
收集的依賴,每一次數據的更新都須要從新收集依賴, 流程以下:setter
=> notify => run
=> get
複製代碼
當數據發佈更新後,會調用notify
方法,notify
會調用run
方法,run
方法會調用get
方法,從新獲取值,從新進行依賴收集。舉一個上面的例子,若是咱們更改了message
的值,而且模板依賴了新更改的值,this.message = {key: 'val'}
,由於上一輪沒有對新值進行依賴,因此這一輪須要從新收集依賴。
在Vue
初始化的時候,會生成一個watcher
,依賴收集就是經過屬性的getter
完成的。結合文章開頭給出的圖片,Observer
和Dep
是一對一的關係,Dep
和Watcher
是多對多的關係,Dep
則是Observer
和Watcher
之間的紐帶。依賴收集完成偶,當屬性變化會執行被Observer
對象的dep.notify()
方法,這個方法會遍歷訂閱者Watcher
列表向其發送消息,Watcher
會執行run
方法去更新視圖。
原本還想講點computed
的,可是估計您看着也累,我寫着也累,computed
將由另一篇文章進行講解。
一篇文章寫下來,很有些難度。下面有三點:
因此給出一些措施來彌補這些問題: