最近一段時間在閱讀Vue源碼,從它的核心原理入手,開始了源碼的學習,而其核心原理就是其數據的響應式。而且結合設計模式進行學習html
這裏簡短的介紹這兩種模式的聯繫和差別,react
觀察者模式定義了對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知,並自動更新。觀察者模式屬於行爲型模式,行爲型模式關注的是對象之間的通信,觀察者模式就是觀察者和被觀察者之間的通信。git
觀察者模式有一個別名叫「發佈-訂閱模式」,或者說是「訂閱-發佈模式」,訂閱者和訂閱目標是聯繫在一塊兒的,當訂閱目標發生改變時,逐個通知訂閱者。咱們能夠用報紙期刊的訂閱來形象的說明,當你訂閱了一份報紙,天天都會有一份最新的報紙送到你手上,有多少人訂閱報紙,報社就會發多少份報紙,報社和訂報紙的客戶就是上面文章開頭所說的「一對多」的依賴關係。github
其實24種基本的設計模式中並無發佈訂閱模式,上面也說了,他只是觀察者模式的一個別稱。設計模式
可是通過時間的沉澱,彷佛他已經強大了起來,已經獨立於觀察者模式,成爲另一種不一樣的設計模式。數組
在如今的發佈訂閱模式中,稱爲發佈者的消息發送者不會將消息直接發送給訂閱者,這意味着發佈者和訂閱者不知道彼此的存在。在發佈者和訂閱者之間存在第三個組件,稱爲調度中心或事件通道,它維持着發佈者和訂閱者之間的聯繫,過濾全部發布者傳入的消息並相應地分發它們給訂閱者。閉包
舉一個例子,你在微博上關注了A,同時其餘不少人也關注了A,那麼當A發佈動態的時候,微博就會爲大家推送這條動態。A就是發佈者,你是訂閱者,微博就是調度中心,你和A是沒有直接的消息往來的,全是經過微博來協調的(你的關注,A的發佈動態)。異步
能夠看出,發佈訂閱模式相比觀察者模式多了個事件通道,事件通道做爲調度中心,管理事件的訂閱和發佈工做,完全隔絕了訂閱者和發佈者的依賴關係。即訂閱者在訂閱事件的時候,只關注事件自己,而不關心誰會發布這個事件;發佈者在發佈事件的時候,只關注事件自己,而不關心誰訂閱了這個事件。函數
觀察者模式有兩個重要的角色,即目標和觀察者。在目標和觀察者之間是沒有事件通道的。一方面,觀察者要想訂閱目標事件,因爲沒有事件通道,所以必須將本身添加到目標(Subject) 中進行管理;另外一方面,目標在觸發事件的時候,也沒法將通知操做(notify) 委託給事件通道,所以只能親自去通知全部的觀察者。oop
當咱們在data
中定義一個值的時候,以下:
const vm = new Vue({ data() { return { message: '' } }, template: '<div>{{message}}</div>' }) vm.message = 'hello';
此時Vue
內部發生了什麼,下面列出須要解決的問題以下:
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
將由另一篇文章進行講解。
一篇文章寫下來,很有些難度。下面有三點:
因此給出一些措施來彌補這些問題:
第一次寫這種源碼分析文章,諸多不足,歡迎你們提出寶貴的建議,也請多多關注個人GitHub~~