Vue3中的響應(yīng)式原理
響應(yīng)式原理
Vue2中使用的是Object.defineProperty,而Vue3使用的是ES6新增的Proxy
先復(fù)習(xí)下它倆的相關(guān)知識。
Object.defineProperty
Object.defineProperty()?是直接在對象上定義新屬性,或修改對象上的現(xiàn)有屬性,然后返回該對象。
語法如下:
descript有兩種形式:數(shù)據(jù)描述符和存取描述符。一個描述符只能是這兩者中的一個,不能同時是兩者,意味著使用了數(shù)據(jù)描述符就不能同時使用存取描述符。
數(shù)據(jù)描述符:決定屬性的值以及該值是否可寫,可選鍵值有:
value:默認(rèn)值為?undefined?,決定該屬性所對應(yīng)的值,可以是任何有效值。
writable:默認(rèn)值為?false?,只有當(dāng)鍵值為?true?時,value?才能被賦值運(yùn)算符改變。
具體使用:
存取描述符:由 getter 和 setter 函數(shù)所描述的屬性,可選鍵值有:
get:默認(rèn)值為?undefined?,值應(yīng)為一個?getter()?函數(shù),當(dāng)訪問該屬性時會自動調(diào)用該函數(shù),返回的返回值會被用作屬性的值。
set:默認(rèn)值為?undefined?,值應(yīng)為一個?setter()?函數(shù),當(dāng)該屬性被修改時,會調(diào)用此函數(shù)。
具體使用:

由此可見使用?Object.defineProperty()?代理監(jiān)聽不到?對象新增屬性?與?數(shù)組的新增修改?。
差點(diǎn)忘了說,兩種描述符都是對象形式。他們還共享以下屬性:
configurable(默認(rèn)false) 是否可配置,為?true?時,屬性描述符才能夠被改變,同時屬性可以從對應(yīng)對象上被刪除。
enumerable(默認(rèn)false) 是否可枚舉,為?true?時,屬性才會出現(xiàn)在對象的枚舉屬性中。
Proxy
Proxy?主要用于改變對象的默認(rèn)訪問行為,實(shí)際上是在訪問對象之前增加一層攔截 感覺與servlet里的攔截器差不多?? ?在任何對對象的訪問行為都會通過這層攔截,如對對象屬性的讀、寫、刪除等。在這層攔截中我們可以增加自定義的行為。
基本語法:
有一句話叫打狗還要看主人,這里狗就是target,其主人就是對應(yīng)的Proxy實(shí)例。打狗這個行為,可以看作是對狗的一個交互行為。你要打狗之前,得先看看狗主人的看法。狗主人能夠決定你能不能打狗,決定的過程就是handler。
一個簡單實(shí)例:
我們通過?Proxy 構(gòu)造器創(chuàng)建了?target?的代理?yushen?,此時對 yushen 屬性的訪問都會轉(zhuǎn)發(fā)到 target 上,通過自定義的 handler 對象進(jìn)行相應(yīng)的攔截操作。
代理可以攔截JavaScript引擎內(nèi)部的底層對象操作,這些操作被攔截后會觸發(fā)相應(yīng)特定操作的陷阱函數(shù),如例子中的get與set就是陷阱函數(shù)。
總結(jié)一下二者區(qū)別:
Proxy是對整個對象的代理,而Object.defineProperty只能代理某個屬性。
對象上新增屬性,數(shù)組新增修改。Proxy可以監(jiān)聽到,Object.defineProperty不能。
若對象內(nèi)部屬性要全部遞歸代理,Proxy可以只在調(diào)用的時候遞歸,而Object.definePropery需要一次完成所有遞歸,性能比Proxy差。
Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。
Proxy使用上比Object.defineProperty方便多。
Vue3中的響應(yīng)式:
讓我們來看看這個場景:
可以發(fā)現(xiàn)兩次輸出的總價都是10,因?yàn)楦淖?price 并不會讓total再跑一次。
我們想要做的,就是讓 price 改變時,讓與 price 相關(guān)的語句都重新再跑一次。該怎么做呢?
由于是運(yùn)行同樣的語句,我們很容易想到將這段語句提取出來封裝成一個函數(shù),并將其保存起來以供之后的調(diào)用。
我們要把 effect 保存起來,需要用到 track() 函數(shù),并在保存后運(yùn)行一次 effect() 。在之后的某個時刻,調(diào)用 trigger() 來運(yùn)行所有以及存儲了的代碼。
那么如何保存 effect 呢?
我們可以使用 Set 來保存,Set 不允許重復(fù)值,如果我們嘗試添加相同的 effect,它不會變成兩個。
運(yùn)行下試試:
這樣我們就完成了 price 這個屬性的依賴關(guān)系。但一個對象并不止有一個屬性,要實(shí)現(xiàn)一個對象的響應(yīng)式,其每個屬性都應(yīng)該有自己的 dep(依賴關(guān)系),或者說 effect 的 Set 集合。那么這又該如何存儲呢?
我們可以使用一個 map 集合。每個屬性的屬性名作為 key ,屬性對應(yīng)的 effect 的 Set 集合作為 map 的 value 。
現(xiàn)在我們對不同的屬性有了跟蹤依賴關(guān)系的方法。但如果我們有多個響應(yīng)式對象呢?
我們可以使用 WeakMap ,將每一個對象名(target)作為 key,depsMap?作為 value。
WeakMap:key必須是對象,對于對象是弱引用。沒用的對象就不會影響垃圾回收機(jī)制,優(yōu)化了性能。
整個數(shù)據(jù)結(jié)構(gòu)如下:

但我們發(fā)現(xiàn),track() 和 trigger() 現(xiàn)在還需要我們自己手動去調(diào)用,這太麻煩了。能不能讓其自動調(diào)用呢?
讓我們想想,一般我們在讀取屬性值的時候需要其去調(diào)用track(),在改變屬性值后需要調(diào)用? trigger() 。對應(yīng)的行為即 get 和 set 。是不是很熟悉,沒錯,這時候就要使用我們的 Proxy 了。
這里模仿了Vue3的 reactive 函數(shù),其返回一個 Proxy 實(shí)例。
可以發(fā)現(xiàn),這個對象已經(jīng)變?yōu)轫憫?yīng)式對象了。改變 price 或是 quantity 的值時,total 值都會實(shí)時更新。
但是,我們還應(yīng)該把 effect 進(jìn)行集中管理,不然當(dāng)一個屬性對應(yīng)的 effect 變多時,代碼會變得冗余。并且在添加 effect 時,effect 應(yīng)該自動運(yùn)行一次。
改造后的代碼如下:
這樣我們就大概實(shí)現(xiàn)了一個基本的響應(yīng)式功能。讓我們?nèi)y試一下。
測試 html 代碼如下:
運(yùn)行結(jié)果:
????



最后
感謝你能看到這里 雖然我感覺我寫成這b樣也沒人看? ?。如果有什么疑惑或是發(fā)現(xiàn)了什么錯誤,歡迎評論區(qū)交流噢。