寫在前面
這道題目是面試中相當高頻的一道題目了,但凡你簡歷上有寫:“熟練使用Vue并閱讀過其部分原始碼”,那么這道題目十有八九面試官都會去問你,
什么?你簡歷上不寫閱讀過原始碼,那面試官也很有可能會問你是否閱讀過回應式相關的原始碼
還是那句歌詞唱的:
掙不脫 逃不過
眉頭解不開的結
命中解不開的劫

整體流程
作為一個前端的MVVM框架,Vue的基本思路和Angular、React并無二致,其核心就在于: 當資料變化時,自動去重繪頁面DOM,這使得我們能從繁瑣的DOM操作中解放出來,從而專心地去處理業務邏輯,
這就是Vue的資料雙向系結(又稱回應式原理),資料雙向系結是Vue最獨特的特性之一,此處我們用官方的一張流程圖來簡要地說明一下Vue回應式系統的整個流程:

在Vue中,每個組件實體都有相應的watcher實體物件,它會在組件渲染的程序中把屬性記錄為依賴,之后當依賴項的setter被呼叫時,會通知watcher重新計算,從而致使它關聯的組件得以更新,
這是一個典型的觀察者模式,
關鍵角色
在 Vue 資料雙向系結的實作邏輯里,有這樣三個關鍵角色:
-
Observer: 它的作用是給物件的屬性添加getter和setter,用于依賴收集和派發更新 -
Dep: 用于收集當前回應式物件的依賴關系,每個回應式物件包括子物件都擁有一個Dep實體(里面subs是Watcher實體陣列),當資料有變更時,會通過dep.notify()通知各個watcher, -
Watcher: 觀察者物件 , 實體分為渲染 watcher (render watcher),計算屬性 watcher (computed watcher),偵聽器 watcher(user watcher)三種
Watcher 和 Dep 的關系
為什么要單獨拎出來一小節專門來說這個問題呢?因為大部分同學只是知道:Vue的回應式原理是通過Object.defineProperty實作的,被Object.defineProperty系結過的物件,會變成「回應式」化,也就是改變這個物件的時候會觸發get和set事件,
但是對于里面具體的物件依賴關系并不是很清楚,這樣也就給了面試官一種:你只是背了答案,對于回應式的內部實作細節,你并不是很清楚的印象,
關于Watcher 和 Dep 的關系這個問題,其實剛開始我也不是很清楚,在查閱了相關資料后,才逐漸對里面的具體實作有了清晰的理解,

剛接觸Dep這個詞的同學都會比較懵: Dep究竟是用來做什么的呢?我們通過defineReactive方法將data中的資料進行回應式后,雖然可以監聽到資料的變化了,那我們怎么處理通知視圖就更新呢?
Dep就是幫我們依賴管理的,
如上圖所示:一個屬性可能有多個依賴,每個回應式資料都有一個Dep來管理它的依賴,
一段話總結原理
上面說了那么多,下面我總結一下Vue回應式的核心設計思路:
當創建Vue實體時,vue會遍歷data選項的屬性,利用Object.defineProperty為屬性添加getter和setter對資料的讀取進行劫持(getter用來依賴收集,setter用來派發更新),并且在內部追蹤依賴,在屬性被訪問和修改時通知變化,
每個組件實體會有相應的watcher實體,會在組件渲染的程序中記錄依賴的所有資料屬性(進行依賴收集,還有computed watcher,user watcher實體),之后依賴項被改動時,setter方法會通知依賴與此data的watcher實體重新計算(派發更新),從而使它關聯的組件重新渲染,
到這里,我們已經了解了“套路”,下面讓我們用偽代碼來實作一下Vue的回應式吧!
核心實作
/**
* @name Vue資料雙向系結(回應式系統)的實作原理
*/
// observe方法遍歷并包裝物件屬性
function observe(target) {
// 若target是一個物件,則遍歷它
if (target && typeof target === "Object") {
Object.keys(target).forEach((key) => {
// defineReactive方法會給目標屬性裝上“監聽器”
defineReactive(target, key, target[key]);
});
}
}
// 定義defineReactive方法
function defineReactive(target, key, val) {
const dep = new Dep();
// 屬性值也可能是object型別,這種情況下需要呼叫observe進行遞回遍歷
observe(val);
// 為當前屬性安裝監聽器
Object.defineProperty(target, key, {
// 可列舉
enumerable: true,
// 不可配置
configurable: false,
get: function () {
return val;
},
// 監聽器函式
set: function (value) {
dep.notify();
},
});
}
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach((sub) => {
sub.update();
});
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/297352.html
標籤:其他
下一篇:nginx的使用
