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

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

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

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

簡(jiǎn)單介紹一下Vue中的響應(yīng)式原理
使用Vue3時(shí)遇到的一些問(wèn)題

評(píng)論