淺析一下Vue3的響應(yīng)式原理
Proxy
Vue3 的響應(yīng)式原理依賴了 Proxy 這個(gè)核心 API,通過(guò) Proxy 可以劫持對(duì)象的某些操作。
const?obj?=?{?a:?1?}; const?p?=?new?Proxy(obj,?{ ??get(target,?property,?receiver)?{ ????console.log("get"); ????return?Reflect.get(target,?property,?receiver); ??}, ??set(target,?property,?value,?receiver)?{ ????console.log("set"); ????return?Reflect.set(target,?property,?receiver); ??}, ??has(target,?prop)?{ ????console.log("has"); ????return?Reflect.has(target,?prop); ??}, ??deleteProperty(target,?prop)?{ ????console.log("deleteProperty"); ????return?Reflect.deleteProperty(target,?prop); ??}, }); p.a;?//?輸出?-->?get p.a?=?2;?//?輸出?-->?set "a"?in?p;?//?輸出?-->?has delete?p.a;?//?輸出?-->?deleteProperty
如上例子,我們用 Proxy 代理了 Obj 對(duì)象的屬性訪問(wèn)、屬性賦值、in 操作符、delete 的操作,并進(jìn)行 console.log 輸出。
Reflect
Reflect 是與 Proxy 搭配使用的一個(gè) API,當(dāng)我們劫持了某些操作時(shí),如果需要再把這些操作反射回去,那么就需要 Reflect 這個(gè) API。
由于我們攔截了對(duì)象的操作,所以這些操作該有的功能都喪失了,例如,訪問(wèn)屬性 p.a 應(yīng)該得到 a 屬性的值,但此時(shí)卻不會(huì)有任何結(jié)果,如果我們還想擁有攔截之前的功能,那我們就需要用 Reflect 反射回去。
const?obj?=?{?a:?1?}; const?p?=?new?Proxy(obj,?{ ??get(target,?property,?receiver)?{ ????console.log("get"); ????return?Reflect.get(target,?property,?receiver); ??}, ??set(target,?property,?value,?receiver)?{ ????console.log("set"); ????return?Reflect.set(target,?property,?receiver); ??}, ??has(target,?prop)?{ ????console.log("has"); ????return?Reflect.has(target,?prop); ??}, ??deleteProperty(target,?prop)?{ ????console.log("deleteProperty"); ????return?Reflect.deleteProperty(target,?prop); ??}, });
舉個(gè)例子
以下全文我們都會(huì)通過(guò)這個(gè)例子來(lái)講述 Vue3 響應(yīng)式的原理。
<div?id="app"></div> <script> ??//?創(chuàng)建一個(gè)響應(yīng)式對(duì)象 ??const?state?=?reactive({?counter:?1?}); ??//?立即運(yùn)行一個(gè)函數(shù),當(dāng)響應(yīng)式對(duì)象的屬性發(fā)生改變時(shí)重新執(zhí)行。 ??effect(()?=>?{ ????document.querySelector("#app").innerHTML?=?state.counter; ??}); ??//?2s?后視圖更新 ??setTimeout(()?=>?{ ????state.counter?+=?1; ??},?2000); </script>
我們用 reactive 創(chuàng)建了一個(gè)響應(yīng)式對(duì)象 state,并調(diào)用了 effect 方法,該方法接受一個(gè)副作用函數(shù),effect 的執(zhí)行會(huì)立即調(diào)用副作用函數(shù),并將 state.counter 賦值給 #app.innerHTML;兩秒后,state.counter += 1,此時(shí),effect 的副作用函數(shù)會(huì)重新執(zhí)行,頁(yè)面也會(huì)變成 2.
內(nèi)部的執(zhí)行過(guò)程大概如下圖所示:
- 調(diào)用 reactive() 返回一個(gè) Proxy 代理對(duì)象,并劫持對(duì)象的 get 與 set 操作
- 調(diào)用 effect() 方法時(shí),會(huì)訪問(wèn)屬性 state.counter,此時(shí)會(huì)觸發(fā) proxy 的 get 操作。
- get 方法會(huì)調(diào)用 track() 進(jìn)行依賴收集;建立一個(gè)對(duì)象(state)、屬性(counter)、effect 副作用函數(shù)的依賴關(guān)系;
- set 方法會(huì)調(diào)用 trigger() 進(jìn)行依賴更新;通過(guò)對(duì)象(state)與屬性(coutner)找到對(duì)應(yīng)的 effect 副作用函數(shù),然后重新執(zhí)行。
reactive
reactive 會(huì)返回如下一個(gè) Proxy 對(duì)象
const?reactive?=?(target)?=>?{ ??return?new?Proxy(target,?{ ????get(target,?key,?receiver)?{ ??????const?res?=?Reflect.get(target,?key,?receiver); ??????track(target,?key);?//?收集依賴 ??????if?(isObject(res))?{ ????????//?如果當(dāng)前獲取的屬性值是一個(gè)對(duì)象,則繼續(xù)將為此對(duì)象創(chuàng)建?Proxy?代理 ????????return?reactive(res); ??????} ??????return?res; ????}, ????set(target,?key,?value,?receiver)?{ ??????Reflect.set(target,?key,?value,?receiver); ??????trigger(target,?key);?//?依賴更新 ????}, ??}); };
effect
let?activeEffect; function?effect(fn)?{ ??const?_effect?=?function?reactiveEffect()?{ ????activeEffect?=?_effect; ????fn(); ??}; ??_effect(); }
首先定義全局的 activeEffect,它永遠(yuǎn)指向當(dāng)前正在執(zhí)行的 effect 副作用函數(shù)。effect 為 fn 創(chuàng)建一個(gè)內(nèi)部的副作用函數(shù),然后立即執(zhí)行,此時(shí)會(huì)觸發(fā)對(duì)象的 get 操作,調(diào)用 track() 方法。
effect(()?=>?{ ??// effect 的立即執(zhí)行會(huì)訪問(wèn) state.counter,觸發(fā)了對(duì)象的 get 操作。 ??document.querySelector("#app").innerHTML?=?state.counter; });
track
track 會(huì)建立一個(gè) 對(duì)象(state) => 屬性(counter) => effect 的一個(gè)依賴關(guān)系
const?targetMap?=?new?WeakMap(); function?track(target,?key)?{ ??if?(!activeEffect)?{ ????return; ??} ??let?depsMap?=?targetMap.get(target); ??if?(!depsMap)?{ ????targetMap.set(target,?(depsMap?=?new?Map())); ??} ??let?dep?=?depsMap.get(key); ??if?(!dep)?{ ????depsMap.set(key,?(dep?=?new?Set())); ??} ??if?(!dep.has(activeEffect))?{ ????dep.add(activeEffect); ??} }
執(zhí)行完成成后我們得到一個(gè)如下的數(shù)據(jù)結(jié)構(gòu):
[?//?map?集合 ??{ ????key:?{counter:?1}?//?state?對(duì)象, ????value:?[?//?map?集合 ??????{ ????????key:?"counter", ????????value:?[?//?set ??????????function?reactiveEffect()?{}?//?effect?副作用函數(shù) ????????], ??????} ????], ??}, ];
注意:當(dāng)我們調(diào)用 effect 時(shí),會(huì)將當(dāng)前的副作用函數(shù)賦值給全局的 activeEffect,所以此時(shí)我們可以正確關(guān)聯(lián)其依賴。
trigger
當(dāng)我們給 state.counter 賦值的時(shí)候就會(huì)觸發(fā)代理對(duì)象的 set 操作,從而調(diào)用 trigger 方法
setTimeout(()?=>?{ ??//?給?counter?屬性賦值會(huì)觸發(fā)?set?操作 ??state.counter?+=?1; },?2000);
function?trigger(target,?key)?{ ??const?depsMap?=?targetMap.get(target); ??if?(!depsMap)?return; ??const?effects?=?depsMap.get(key); ??effects?&&?effects.forEach((effect)?=>?effect()); }
以上就是淺析一下Vue3的響應(yīng)式原理的詳細(xì)內(nèi)容,更多關(guān)于Vue3響應(yīng)式原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue.js的簡(jiǎn)單自動(dòng)求和計(jì)算實(shí)例
今天小編就為大家分享一篇vue.js的簡(jiǎn)單自動(dòng)求和計(jì)算實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11ajax請(qǐng)求+vue.js渲染+頁(yè)面加載的示例
下面小編就為大家分享一篇ajax請(qǐng)求+vue.js渲染+頁(yè)面加載的示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02詳解如何從零開(kāi)始搭建Express+Vue開(kāi)發(fā)環(huán)境
這篇文章主要介紹了詳解如何從零開(kāi)始搭建Express+Vue開(kāi)發(fā)環(huán)境,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07vuejs項(xiàng)目打包之后的首屏加載優(yōu)化及打包之后出現(xiàn)的問(wèn)題
這篇文章主要介紹了vuejs項(xiàng)目打包之后的首屏加載優(yōu)化及打包之后可能出現(xiàn)的問(wèn)題,需要的朋友可以參考下2018-04-04SpringBoot+Vue3實(shí)現(xiàn)上傳文件功能
這篇文章主要介紹了SpringBoot+Vue3實(shí)現(xiàn)上傳文件功能,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01