響應式的基本概念
響應式是指當數(shù)據(jù)發(fā)生變化時,系統(tǒng)會自動更新與數(shù)據(jù)相關(guān)的 DOM 結(jié)構(gòu)。
在 Vue2 中,響應式系統(tǒng)的實現(xiàn)基于Object.defineProperty。然而,Object.defineProperty有一些局限,如:無法監(jiān)聽數(shù)組的變化、需要遍歷對象的每個屬性進行監(jiān)聽、性能開銷較大。
在 Vue3 中,響應式系統(tǒng)的實現(xiàn)基于 ES6 的Proxy對象。Proxy可以直接監(jiān)聽對象和數(shù)組的變化,而無需對每個屬性進行監(jiān)聽,從而大大提高性能。同時,Proxy也可以解決Object.defineProperty無法監(jiān)聽數(shù)組的問題。
響應式的關(guān)鍵在于vue的依賴收集機制。
簡化模型
為了更直觀的理解vue依賴收集的模型,我們先來看一個“簡單”的功能描述:
已知watcher函數(shù),調(diào)用了一些“外部函數(shù)”:
function watcher () { console.log('watcher start') 函數(shù)1(); 函數(shù)2(); console.log('watcher end') }
能否設(shè)計一個依賴收集系統(tǒng),使這些“外部函數(shù)”運行時,watcher也會隨之運行?
關(guān)鍵:如何判斷函數(shù)間的調(diào)用關(guān)系?
看似有點難,實際一點也不簡單,我們需要知道函數(shù)間調(diào)用關(guān)系。我們先看個例子:
function A() { console.log('A') } function B() { console.log('B') } function C() { console.log('C') } ... function watcher () { console.log('watcher start!') /* *這里調(diào)用了上面的某些函數(shù)* */ console.log('watcher end!') } /* *這里運行了某些函數(shù)* */ watcher(); - watcher start! - A - B - wathcer end! - C
從運行結(jié)果我們可以看出watcher內(nèi)部一定調(diào)用了A、B函數(shù):
為啥?js是單線程的。
C函數(shù)一定在watcher外面嗎?不一定。例如:
function watcher () { console.log('start') A() B() setTimeout(()=>{ C() }) console.log('end') } watcher();
C函數(shù)這種咋辦?不管!我們只管肯定沒問題的!
我們由此可以確定
函數(shù)watcher執(zhí)行期間,凡是運行過的函數(shù),一定是watcher內(nèi)部調(diào)用過的函數(shù)
根據(jù)這個原理,我們設(shè)計依賴收集系統(tǒng)如下:
// 當前的監(jiān)聽函數(shù) let activeEffect = null // 副作用函數(shù) function effect (watcher) { activeEffect = watcher // watcher執(zhí)行的期間就是依賴收集的階段 watcher(true) activeEffect= null } // isTracking:是否是依賴收集階段 function A (isTracking = false) { if (isTracking) { // 依賴收集階段,effects就是A的監(jiān)聽函數(shù)集合 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函數(shù),監(jiān)聽函數(shù)的觸發(fā)流程抽離為trigger函數(shù);這樣,我們實現(xiàn)了一個簡單的依賴收集系統(tǒng)。
Vue依賴收集模型
我們知道Vue3是通過Proxy實現(xiàn)的依賴收集流程,Proxy示例:
1. Proxy對象get監(jiān)聽,set觸發(fā)
Vue3中,Proxy代理數(shù)據(jù)在被讀取時“依賴收集”,在被賦值時會“觸發(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; } })
測試一下
測試代碼如下:
終端運行結(jié)果:
看起來效果不錯!但是下面的例子里有問題:
一個無關(guān)的屬性key的賦值也會觸發(fā)監(jiān)聽函數(shù)!這不是我們想要的。為了精確監(jiān)聽,還需要細化依賴收集系統(tǒng)。
2. “key”級依賴
我們可以將對象的屬性作為基本單位進行依賴收集。改造如下:
// 依賴收集函數(shù),這里精確到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ā)函數(shù),這里精確到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 }})
這里試一下效果
這樣就實現(xiàn)了精確到屬性的監(jiān)聽系統(tǒng)。看到這里,似乎完成的很不錯了,但是看到下面的例子:
這里value屬性由false變?yōu)閠rue后,屬性data的就已不再參與監(jiān)聽函數(shù)內(nèi)的邏輯了;監(jiān)聽函數(shù)不應該再響應data屬性,但實際上并沒有。因為依賴關(guān)系已經(jīng)固化,data屬性只要變化就一定會觸發(fā)監(jiān)聽,不管是否真的需要:
3. 分支切換
為了優(yōu)化這一點,應將依賴關(guān)系實時更新,將多余的監(jiān)聽去除。為此,vue采取的策略是:
每次監(jiān)聽函數(shù)運行前,都要將自己的依賴關(guān)系清除;然后在運行期間重建依賴關(guān)系。(版權(quán)歸掘金硬毛巾原作者所有,侵刪)
審核編輯:黃飛
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4371瀏覽量
64277 -
DOM
+關(guān)注
關(guān)注
0文章
18瀏覽量
9704 -
監(jiān)聽系統(tǒng)
+關(guān)注
關(guān)注
0文章
7瀏覽量
6462
原文標題:Vue3響應式系統(tǒng)原理
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
一文解析Vue代碼層面的優(yōu)化
基于TypeScript實現(xiàn)Vue3.0指令組件拖拽
Vue框架的教程資料免費下載

關(guān)于vue如何去水印的解決方法的介紹
關(guān)于React和Vue產(chǎn)生一定的認知
Vue入門之Vue定義

如何使用springboot+vue搭建個人網(wǎng)站3

搭建基于Vue3+Vite2+Arco+Typescript+Pinia后臺管理系統(tǒng)模板

簡單介紹一下Vue中的響應式原理
使用Vue3時遇到的一些問題

評論