我們都知道Vue通過MVVM思想實現(xiàn)數(shù)據(jù)的雙向綁定,數(shù)據(jù)驅(qū)動頁面視圖。那它到底是如何進(jìn)行雙向綁定的呢?
Vue數(shù)據(jù)雙向綁定是通過采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式來實現(xiàn)的。通過Object.defineProperty()來劫持各個屬性的setter,getter。修改觸發(fā)set方法賦值,獲取觸發(fā)get方法取值,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的回調(diào)并通過數(shù)據(jù)劫持發(fā)布信息。
Vue 主要通過以下 4 個步驟來實現(xiàn)數(shù)據(jù)雙向綁定的:
實現(xiàn)一個監(jiān)聽器 Observer:對數(shù)據(jù)對象進(jìn)行遍歷,包括子屬性對象的屬性,利用 Object.defineProperty() 對屬性都加上 setter 和 getter。這樣的話,給這個對象的某個值賦值,就會觸發(fā) setter,那么就能監(jiān)聽到了數(shù)據(jù)變化。
實現(xiàn)一個解析器 Compile:解析 Vue 模板指令,將模板中的變量都替換成數(shù)據(jù),然后初始化渲染頁面視圖,并將每個指令對應(yīng)的節(jié)點綁定更新函數(shù),添加監(jiān)聽數(shù)據(jù)的訂閱者,一旦數(shù)據(jù)有變動,收到通知,調(diào)用更新函數(shù)進(jìn)行數(shù)據(jù)更新。
實現(xiàn)一個訂閱者 Watcher:Watcher 訂閱者是 Observer 和 Compile 之間通信的橋梁 ,主要的任務(wù)是訂閱 Observer 中的屬性值變化的消息,當(dāng)收到屬性值變化的消息時,觸發(fā)解析器 Compile 中對應(yīng)的更新函數(shù)。
實現(xiàn)一個訂閱器 Dep:訂閱器采用發(fā)布-訂閱 設(shè)計模式,用來收集訂閱者 Watcher,對監(jiān)聽器 Observer 和 訂閱者 Watcher 進(jìn)行統(tǒng)一管理。

首先我們看一下Object.defineProperty()方法:


其核心就是通過Object.defineProperty()來實現(xiàn)對屬性的劫持,那么在設(shè)置或者獲取的時候我們就可以在get或者set方法里加入其他的觸發(fā)函數(shù),達(dá)到監(jiān)聽數(shù)據(jù)變動的目的。
實現(xiàn)Observer
它的作用是將一個正常的object轉(zhuǎn)換為每個層級的屬性都是響應(yīng)式的。

我們定義了__ob__屬性用來存儲定義的Observer實例。通過walk方法進(jìn)行當(dāng)前層的屬性遍歷并設(shè)置為響應(yīng)式的。

由于存在對象屬性嵌套的情況,所以在遍歷每一個屬性時,我們需要對其子元素進(jìn)行設(shè)置為響應(yīng)式的,至此形成了多個函數(shù)循環(huán)調(diào)用(遞歸)。

數(shù)組的響應(yīng)式
我們都知道Vue 實現(xiàn)數(shù)據(jù)雙向綁定通過 Object.defineProperty() 對數(shù)據(jù)進(jìn)行劫持,但是 Object.defineProperty() 只能對屬性進(jìn)行數(shù)據(jù)劫持,不能對整個對象進(jìn)行劫持,同理無法對數(shù)組進(jìn)行劫持。Vue 框架是通過遍歷數(shù)組和遞歸遍歷對象,從而達(dá)到利用 Object.defineProperty() 也能對對象和數(shù)組(部分方法的操作)進(jìn)行監(jiān)聽。Vue的數(shù)組響應(yīng)式是如何實現(xiàn)的?它是以Array.prototype為原型,創(chuàng)建了一個arrayMethods對象,使用Object.setPrototypeOf()強制讓數(shù)組指向arrayMethods,這樣就可以觸發(fā)我們在arrayMethods中的改寫的數(shù)組操作方法。


實現(xiàn)Dep和Watcher
依賴就是Watcher。只有Watcher觸發(fā)的getter才會收集依賴,哪個Watcher觸發(fā)了getter,就把哪個Watcher收集到Dep中。
Dep使用發(fā)布訂閱模式,當(dāng)數(shù)據(jù)發(fā)生變化時,會循環(huán)依賴列表,把所有的Watcher都通知一遍。
代碼實現(xiàn)的巧妙之處:Watcher把自己設(shè)置到全局的一個指定位置,然后讀取數(shù)據(jù),因為讀取了數(shù)據(jù),所以會觸發(fā)這個數(shù)據(jù)的getter。在getter中就能得到當(dāng)前正在讀取數(shù)據(jù)的Watcher,并把這個Watcher收集到Dep中。



在getter中收集依賴,在setter中觸發(fā)依賴。我們可以監(jiān)聽每個數(shù)據(jù)的變化了,那么監(jiān)聽到變化之后觸發(fā)notify,再調(diào)用訂閱者的update方法。

把依賴收集的代碼封裝成一個Dep類,它專門用來管理依賴,每個Observer的實例,成員中都有一個Dep的實例;
