一、什麼是響應式資料
響應式資料是指當資料發生變化時,相關的檢視或元件會自動更新,保持與資料的同步。這樣的設計使得開發者能夠更方便地管理和更新資料,無需手動操作DOM或顯式地更新檢視。當資料發生變化時,所有使用該資料的地方都會自動更新。
二、觀察者模式
-
觀察者模式定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。
-
在vue中,可以在模板、計算屬性、偵聽器等地方使用定義在data中的資料,可以理解為這三者都觀察著data中對應資料的變化。模板是渲染watcher、計算屬性是計算屬性watcher、偵聽器是使用者watcher。
-
在某個屬性發生變化時,需要去通知使用該屬性的所有地方進行更新,所以該屬性需要將所有依賴該屬性的地方提前收集起來。這個收集所有依賴的容器稱為dep,dep有兩個方法:addSub(收集依賴)、notify(派發更新)。addSub用來儲存watcher,notify用來通知watcher更新。
// 觀察者 class Watcher { constructor(fn) { this.getter = fn; } // 更新函式,用於觸發更新 update() { this.getter(); } } // 依賴收集器 class Dep { constructor() { // 用來儲存 watcher this.subs = []; } // 新增 watcher addSub(watcher) { this.subs.push(watcher); } // 屬性更新,通知所有 watcher 執行對應的更新方法 notify() { this.subs.forEach((watcher) => { watcher.update(); }); } } const watcher1 = new Watcher(() => { console.log("更新Dom"); }); const watcher2 = new Watcher(() => { console.log("更新計算屬性"); }); const dep = new Dep(); dep.addSub(watcher1); dep.addSub(watcher2); // 當屬性變化時,呼叫該屬性的依賴收集器的notify方法,通知所有watcher更新 dep.notify();
三、如何實現響應式資料
資料劫持
:即在資料物件身上定義一些特殊的行為。在Vue2中,是透過Object.defineProperty
方法實現的。這個方法允許我們為物件的屬性定義getter和setter。- 遞迴轉換:如果資料物件是一個複雜的結構(例如,包含其他物件),我們需要遞迴地對其所有屬性進行相同的轉換,以確保無論資料如何巢狀,都能追蹤其變化。
- 依賴收集:當屬性被訪問時(透過getter),需要記錄下哪些檢視或元件依賴於這個屬性。通常是透過一個依賴收集器或觀察者模式實現的。每個依賴都會被新增到一個“依賴列表”中。在下一次屬性被更新時,就可以透過“依賴列表”知道,需要通知哪些檢視或元件去更新。
- 通知更新:當屬性被修改時(透過setter),我們需要通知所有依賴於這個屬性的檢視或元件進行更新。這可以透過遍歷依賴列表並觸發它們的更新方法來實現。
- 最佳化效能:為了最佳化效能,可能需要實現一些額外的功能,如非同步更新佇列和批處理更新。這可以確保多個屬性的變化只觸發一次檢視更新,而不是每次屬性變化都立即更新。
- 處理陣列和特殊方法:對於陣列,我們需要特別處理,因為使用
Object.defineProperty
來對陣列中的每一個元素實現響應式,效能很差,而且無法監聽到陣列長度的變化。所以Vue透過重寫陣列的一些方法來實現對陣列變化的追蹤。 - 提供API:需要提供一套API,讓開發者能夠方便地宣告響應式資料、觀察資料變化以及觸發檢視更新。由於新增屬性和刪除屬性無法監控變化。Vue提供了
$set
、$delete
來實現資料的響應式。
四、Vue2實現響應式資料
- Vue2透過
Object.defineProperty
進行資料劫持,需要遍歷物件為屬性新增getter和setter,效能很差 - 在元件第一次掛載時,會使用某些響應式資料,因此會觸發資料對應的getter方法,就可以在 getter 中進行依賴收集。在後續資料變化時,會觸發setter 方法,就可以在 setter 中通知依賴進行更新
- 新增屬性和刪除屬性監測不到。需要使用
$set
、$delete
來實現資料的響應式 - 由於陣列的元素可能有很多,迴圈遍歷為每一個元素新增getter和setter效能太差,所以Vue2不對基本型別的陣列元素進行響應式處理,而是重寫陣列相關的方法。
- 對於ES6中新產生的Map、Set等資料結構不支援