開篇
最近在學習Vue的原始碼,看了網上一些大神的部落格,看起來感覺還是蠻吃力的。自己記錄一下學習的理解,希望能夠達到簡單易懂,不看原始碼也能理解的效果?
如果有錯誤,懇求大佬們指點嘿?
Object.defineProperty
相信很多同學或多或少都瞭解Vue的響應式原理是通過Object.defineProperty
實現的。被Object.defineProperty
繫結過的物件,會變成「響應式」化。也就是改變這個物件的時候會觸發get和set事件。進而觸發一些檢視更新。舉個例子?
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('我被讀了,我要不要做點什麼好?');
return val;
},
set: newVal => {
if (val === newVal) {
return;
}
val = newVal;
console.log("資料被改變了,我要把新的值渲染到頁面上去!");
}
})
}
let data = {
text: 'hello world',
};
// 對data上的text屬性進行繫結
defineReactive(data, 'text', data.text);
console.log(data.text); // 控制檯輸出 <我被讀了,我要不要做點什麼好?>
data.text = 'hello Vue'; // 控制檯輸出 <hello Vue && 資料被改變了,我要把新的值渲染到頁面上去!>
複製程式碼
Observer 「響應式」
Vue
中用Observer
類來管理上述響應式化Object.defineProperty
的過程。我們可以用如下程式碼來描述,將this.data
也就是我們在Vue
程式碼中定義的data
屬性全部進行「響應式」繫結。
class Observer {
constructor() {
// 響應式繫結資料通過方法
observe(this.data);
}
}
export function observe (data) {
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
// 將data中我們定義的每個屬性進行響應式繫結
defineReactive(obj, keys[i]);
}
}
複製程式碼
Dep 「依賴管理」
什麼是依賴?
相信沒有看過原始碼或者剛接觸Dep
這個詞的同學都會比較懵。那Dep
究竟是用來做什麼的呢?
我們通過defineReactive
方法將data
中的資料進行響應式後,雖然可以監聽到資料的變化了,那我們怎麼處理通知檢視就更新呢?
Dep
就是幫我們收集【究竟要通知到哪裡的】。比如下面的程式碼案例,我們發現,雖然data
中有text
和message
屬性,但是隻有message
被渲染到頁面上,至於text
無論怎麼變化都影響不到檢視的展示,因此我們僅僅對message
進行收集即可,可以避免一些無用的工作。
那這個時候message
的Dep
就收集到了一個依賴,這個依賴就是用來管理data
中message
變化的。
<div>
<p>{{message}}</p>
</div>
複製程式碼
data: {
text: 'hello world',
message: 'hello vue',
}
複製程式碼
當使用watch
屬性時,也就是開發者自定義的監聽某個data中屬性的變化。比如監聽message
的變化,message
變化時我們就要通知到watch
這個鉤子,讓它去執行回撥函式。
這個時候message
的Dep
就收集到了兩個依賴,第二個依賴就是用來管理watch
中message
變化的。
watch: {
message: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
}
複製程式碼
當開發者自定義computed
計算屬性時,如下messageT
屬性,是依賴message
的變化的。因此message
變化時我們也要通知到computed
,讓它去執行回撥函式。
這個時候message
的Dep
就收集到了三個依賴,這個依賴就是用來管理computed
中message
變化的。
computed: {
messageT() {
return this.message + '!';
}
}
複製程式碼
圖示如下:一個屬性可能有多個依賴,每個響應式資料都有一個Dep
來管理它的依賴。
如何收集依賴
我們如何知道data
中的某個屬性被使用了,答案就是Object.defineProperty
,因為讀取某個屬性就會觸發get
方法。可以將程式碼進行如下改造:
function defineReactive (obj, key, val) {
let Dep; // 依賴
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('我被讀了,我要不要做點什麼好?');
// 被讀取了,將這個依賴收集起來
Dep.depend(); // 本次新增
return val;
},
set: newVal => {
if (val === newVal) {
return;
}
val = newVal;
// 被改變了,通知依賴去更新
Dep.notify(); // 本次新增
console.log("資料被改變了,我要把新的值渲染到頁面上去!");
}
})
}
複製程式碼
什麼是依賴
那所謂的依賴究竟是什麼呢?上面的圖中已經暴露了答案,就是Watcher
。
Watcher 「中介」
Watcher
就是類似中介的角色,比如message
就有三個中介,當message
變化,就通知這三個中介,他們就去執行各自需要做的變化。
Watcher
能夠控制自己屬於哪個,是data
中的屬性的還是watch
,或者是computed
,Watcher
自己有統一的更新入口,只要你通知它,就會執行對應的更新方法。
因此我們可以推測出,Watcher
必須要有的2個方法。一個就是通知變化,另一個就是被收集起來到Dep中去。
class Watcher {
addDep() {
// 我這個Watcher要被塞到Dep裡去了~~
},
update() {
// Dep通知我更新呢~~
},
}
複製程式碼
總結
回顧一下,Vue
響應式原理的核心就是Observer
、Dep
、Watcher
。
Observer
中進行響應式的繫結,在資料被讀的時候,觸發get
方法,執行Dep
來收集依賴,也就是收集Watcher
。
在資料被改的時候,觸發set
方法,通過對應的所有依賴(Watcher
),去執行更新。比如watch
和computed
就執行開發者自定義的回撥方法。
本篇文章屬於入門篇,能夠先簡單的理解Observer
、Dep
、Watcher
三者的作用和關係。後面會逐漸詳細和深入,循序漸進的理解和學習。
如果你覺得對你有幫助,就點個贊吧~
正在書寫的系列~
- 1. Vue響應式原理-理解Observer、Dep、Watcher
- 2. 響應式原理-如何監聽Array的變化
- 3. 響應式原理-如何監聽Array的變化?詳細版
- 4. Vue非同步更新 - nextTick為什麼要microtask優先?
Github部落格 歡迎交流~