響應式的基本概念
響應式是指當數據發(fā)生變化時,系統(tǒng)會自動更新與數據相關的 DOM 結構。
在 Vue2 中,響應式系統(tǒng)的實現基于Object.defineProperty。然而,Object.defineProperty有一些局限,如:無法監(jiān)聽數組的變化、需要遍歷對象的每個屬性進行監(jiān)聽、性能開銷較大。
在 Vue3 中,響應式系統(tǒng)的實現基于 ES6 的Proxy對象。Proxy可以直接監(jiān)聽對象和數組的變化,而無需對每個屬性進行監(jiān)聽,從而大大提高性能。同時,Proxy也可以解決Object.defineProperty無法監(jiān)聽數組的問題。
響應式的關鍵在于vue的依賴收集機制。
簡化模型
為了更直觀的理解vue依賴收集的模型,我們先來看一個“簡單”的功能描述:
已知watcher函數,調用了一些“外部函數”:
function watcher () { console.log('watcher start') 函數1(); 函數2(); console.log('watcher end') }
能否設計一個依賴收集系統(tǒng),使這些“外部函數”運行時,watcher也會隨之運行?
關鍵:如何判斷函數間的調用關系?
看似有點難,實際一點也不簡單,我們需要知道函數間調用關系。我們先看個例子:
function A() { console.log('A') }
function B() { console.log('B') }
function C() { console.log('C') }
...
function watcher () {
console.log('watcher start!')
/* *這里調用了上面的某些函數* */
console.log('watcher end!')
}
/* *這里運行了某些函數* */
watcher();
- watcher start!
- A
- B
- wathcer end!
- C
從運行結果我們可以看出watcher內部一定調用了A、B函數:
為啥?js是單線程的。
C函數一定在watcher外面嗎?不一定。例如:
function watcher () { console.log('start') A() B() setTimeout(()=>{ C() }) console.log('end') } watcher();
C函數這種咋辦?不管!我們只管肯定沒問題的!
我們由此可以確定
函數watcher執(zhí)行期間,凡是運行過的函數,一定是watcher內部調用過的函數
根據這個原理,我們設計依賴收集系統(tǒng)如下:
// 當前的監(jiān)聽函數 let activeEffect = null // 副作用函數 function effect (watcher) { activeEffect = watcher // watcher執(zhí)行的期間就是依賴收集的階段 watcher(true) activeEffect= null } // isTracking:是否是依賴收集階段 function A (isTracking = false) { if (isTracking) { // 依賴收集階段,effects就是A的監(jiān)聽函數集合 A.effects = A.effects || new Set() A.effects.add(activeEffect) } else { // 依賴運行階段 console.log('A觸發(fā)了') A.effects.forEach(fn => fn(true)) } } function B (isTracking = false) { /*** 與A類似 ***/ }
測試一下效果


看起來達到了要求。
將上面代碼優(yōu)化一下,最終如下:
let activeEffect = null;
function effect (watcher) {
activeEffect = watcher;
watcher(true);
activeEffect = null;
}
const bucket = new WeakMap();
function track (target) {
const effects = bucket.get(target) || new Set();
activeEffect && effects.add(activeEffect);
bucket.set(target, effects);
}
function trigger (target) {
bucket.get(target)?.forEach?.(fn => fn(true));
}
function A (isTracking = false) {
if (isTracking) {
track(A);
} else {
console.log('A觸發(fā)了')
trigger(A);
}
}
function B (isTracking = false) {
}
這里將之前 A.effects = A.effects || new Set();依賴收集流程提取成track函數,監(jiān)聽函數的觸發(fā)流程抽離為trigger函數;這樣,我們實現了一個簡單的依賴收集系統(tǒng)。
Vue依賴收集模型
我們知道Vue3是通過Proxy實現的依賴收集流程,Proxy示例:

1. Proxy對象get監(jiān)聽,set觸發(fā)
Vue3中,Proxy代理數據在被讀取時“依賴收集”,在被賦值時會“觸發(fā)依賴”;我們試一下上面完成的依賴收集系統(tǒng),看下效果:
const data = {
value: 1,
}
const proxyData = new Proxy(data, {
get(target, key) {
track(target);
return target[key];
},
set(target, key, value) {
trigger(target);
target[key] = value;
}
})
測試一下
測試代碼如下:

終端運行結果:

看起來效果不錯!但是下面的例子里有問題:


一個無關的屬性key的賦值也會觸發(fā)監(jiān)聽函數!這不是我們想要的。為了精確監(jiān)聽,還需要細化依賴收集系統(tǒng)。
2. “key”級依賴
我們可以將對象的屬性作為基本單位進行依賴收集。改造如下:
// 依賴收集函數,這里精確到keyfunction track (target, key) { const effects = bucket.get(target) || new Map(); const keyMap = effects.get(key) || new Set(); effects.set(key, keyMap); bucket.set(target, effects); activeEffect && keyMap.add(activeEffect);}// 依賴觸發(fā)函數,這里精確到keyfunction trigger (target, key) { const effects = bucket.get(target); if (!effects) return; const keyMap = effects.get(key); if (!keyMap) return; keyMap.forEach(effect => effect());}
const data = { value: 1}const proxyData = new Proxy(data, { get(target, key) {
// 具體到key進行收集 track(target, key); return target[key] }, set(target, key, value) {
// 觸發(fā)到key trigger(target, key); target[key] = value }})
這里試一下效果


這樣就實現了精確到屬性的監(jiān)聽系統(tǒng)??吹竭@里,似乎完成的很不錯了,但是看到下面的例子:

這里value屬性由false變?yōu)閠rue后,屬性data的就已不再參與監(jiān)聽函數內的邏輯了;監(jiān)聽函數不應該再響應data屬性,但實際上并沒有。因為依賴關系已經固化,data屬性只要變化就一定會觸發(fā)監(jiān)聽,不管是否真的需要:

3. 分支切換
為了優(yōu)化這一點,應將依賴關系實時更新,將多余的監(jiān)聽去除。為此,vue采取的策略是:
每次監(jiān)聽函數運行前,都要將自己的依賴關系清除;然后在運行期間重建依賴關系。(版權歸掘金硬毛巾原作者所有,侵刪)
審核編輯:黃飛
-
函數
+關注
關注
3文章
4422瀏覽量
67869 -
DOM
+關注
關注
0文章
18瀏覽量
9880 -
監(jiān)聽系統(tǒng)
+關注
關注
0文章
7瀏覽量
6515
原文標題:Vue3響應式系統(tǒng)原理
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
Vue3 + React18 + TS4入門到實戰(zhàn) 系統(tǒng)學習3大熱門技術 | 更新完結
一文解析Vue代碼層面的優(yōu)化
基于TypeScript實現Vue3.0指令組件拖拽
關于vue如何去水印的解決方法的介紹
關于React和Vue產生一定的認知
搭建基于Vue3+Vite2+Arco+Typescript+Pinia后臺管理系統(tǒng)模板
簡單介紹一下Vue中的響應式原理
使用Vue3時遇到的一些問題
一文看懂Vue3響應式系統(tǒng)原理
評論