大家都知道在vue3中響應(yīng)式原理有原先的object.defineProperty換成了es6的新特性Proxy桑驱。
那Proxy到底是什么呢,他和object.defineProperty又有什么區(qū)別脉顿,他的出現(xiàn)解決了vue2的哪些痛點?
首先來回答Proxy到底是什么:
可以簡單的理解為一個攔截器点寥,他可以對任何對象的絕大部分行為和操作進行干涉艾疟,也就是我們常說的攔截。一提到攔截是不是就有感覺了敢辩。有了攔截不就可以實現(xiàn)響應(yīng)式了嘛蔽莱!也的確如此,對new Proxy返回的代理對象進行大部分操作如增刪改讀都可以被攔截到戚长,我們就可以在其中做一些其他的事情盗冷。Proxy當然不止這些功能,他提供了多達13種行為的攔截同廉,具體的可以自行查閱文檔
本文只介紹get set 以及deleteProperty仪糖。
回到第二個問題以及第三個問題,他和object.defineProperty的區(qū)別在哪兒迫肖,我們都知道object.defineProperty只能攔截到對象自身以有的屬性锅劝,而后續(xù)新增的屬性是無法攔截到的,所以vue2提供了$set等一系列的api來幫助完善響應(yīng)式蟆湖。并且object.defineProperty并沒有辦法攔截到數(shù)組的一些變更故爵,而vue2則采用了重寫js種能修改源數(shù)據(jù)的數(shù)組api來實現(xiàn)數(shù)組的響應(yīng)式,這些問題在proxy中都不存在了隅津,這就是二者區(qū)別以及他的出現(xiàn)解決了vue2的哪些問題
那接下來就一起來看看他是怎么使用的吧稠集。
語法如下
const proxyObj = new Proxy(target,{
get(target,key){
},
set(target,key,val){
},
deleteProperty(target,key){
}
...
})
其中target就是需要被代理的對象,而當我們修改讀取新增或刪除代理對象的某些值的時候就會觸發(fā)一系列相關(guān)的函數(shù)饥瓷,也就是我們在new Proxy的時候傳入的第二個對象中的函數(shù)。
這里會有一個疑問痹籍,修改proxyObj的時候會不會影響到原來的對象呢铆,以及直接修改原來的對象,proxyObj會被修改嗎蹲缠。
經(jīng)過實驗我發(fā)現(xiàn)他們兩個對象是互通的棺克,也就是你改了我也會變我改了你也會變,只不過如果直接修改原對象則不會觸發(fā)攔截线定。
至于是為什么娜谊,我的理解是在所有的攔截操作中,他都接收了target也就是原對象作為參數(shù)斤讥,那實際的操作也是在target上進行的纱皆,所以可以理解為target和proxyObj其實就是同一個對象,只不過proxyObj攔截你的各種操作,然后分發(fā)到原對象身上去而已派草。不知道理解的是不是有誤搀缠。。
接下來我們就來手動實現(xiàn)一下vue3中的王牌響應(yīng)式api reactive和ref
要實現(xiàn)他們倆就得先知道他們倆是干什么的近迁,其實兩個api都是包裝數(shù)據(jù)用的艺普,使數(shù)據(jù)變?yōu)轫憫?yīng)式,唯一的不同點就是reactive用于包裝對象和數(shù)組ref則可以用于包裝各種類型鉴竭。經(jīng)過ref包裝后的值需要通過.value的方式獲取歧譬。其實ref在包裝對象和數(shù)組類型的時候也是調(diào)用的reactive,具體的步驟看后續(xù)實現(xiàn)就行搏存。
現(xiàn)在實現(xiàn)一個reactive
const reactiveHandler = {
get(target,key){
console.log('攔截到了讀取數(shù)據(jù)',key)
return Reflect.get(target,key)
},
set(target,key,val){
console.log('攔截到了更新或新增數(shù)據(jù)',key)
return Reflect.set(target,key,val)
},
deleteProperty(target,key){
console.log('攔截到了刪除數(shù)據(jù)',key)
return Reflect.deleteProperty(target,key)
}
}
function reactive(target) {
if(target&&typeof target==='object'){
if(Array.isArray(target)){
target.forEach((d,index)=>{
target[index] = reactive(d)
})
}else{
Object.keys(target).forEach(d=>{
target[d] = reactive(target[d])
})
}
return new Proxy(target,reactiveHandler)
}
return target
}
可以看到還是比較簡單的瑰步,這里只實現(xiàn)了一下攔截,并沒有實現(xiàn)更新視圖祭埂。因為Proxy只能攔截到目標對象自身屬性的變化面氓,但是如果自身某個屬性也是引用類型的話,他就攔截不到了蛆橡。
比如當我們操作obj.user的時候舌界,user是obj的屬性,這個時候Proxy是完全可以攔截到任意行為的泰演,但是如果user也是一個對象呻拌,我們要去操作obj.user.name的時候。此時Proxy就只能攔截到obj.user睦焕,并不能攔截到name屬性的任意行為了藐握。所以上文中reactive函數(shù)做了一個遞歸處理,將所有的對象和數(shù)組都轉(zhuǎn)為代理形式垃喊。
vue3中還有個與reactive對應(yīng)的api叫shallowReactive猾普,他就是淺層的響應(yīng)式,也就是只作用于目標對象自身屬性本谜,再往深了去就不進行代理了初家,他的實現(xiàn)就更簡單了
function shallowReactive(target) {
if(target&&typeof target==='object'){
return new Proxy(target,reactiveHandler)
}
return target
}
接下來去實現(xiàn)ref。
我們都知道proxy是用來代理對象的乌助,但是js中可不止有對象和數(shù)組這兩個數(shù)據(jù)類型溜在。那當我們需要對基本數(shù)據(jù)類型進行響應(yīng)式的時候該怎么辦呢。這個時候ref就出現(xiàn)了他托。他其實就是利用了對象的get props 和set props函數(shù)掖肋。
{
_num:0,
get num(){
console.log('劫持到了get')
return this._num
},
set num(val){
console.log('劫持到了set',val)
this._num= val
}
}
是不是很驚訝,對象中還有這種用法赏参,get 和set可以指定一個屬性名志笼,當我們在訪問這個屬性名的時候就會觸發(fā)get和set方法沿盅。就可以實現(xiàn)攔截的效果拉。現(xiàn)在是不是明白了為什么ref包裹的數(shù)據(jù)要.value的形式去訪問了籽腕?
function ref(target) {
return{
_value:reactive(target),
get value(){
console.log('劫持到了get')
return this._value
},
set value(val){
console.log('劫持到了set',val)
this._value = val
}
}
}
其實ref 的內(nèi)部就是這樣的拉嗡呼。我們在訪問value屬性的時候,所有的操作都會分發(fā)到_value身上去皇耗。那如果這個時候傳入的是對象或者數(shù)組的話怎么辦呢南窗,很簡單。用之前寫好的reactive函數(shù)包裹一次就行拉郎楼。
因為對象中的get和set同樣是只能監(jiān)聽到你指定的那個屬性的變化的万伤,如果你指定的那個屬性是對象的話,那么深層的變化的是監(jiān)聽不到的呜袁。所以需要借助reactive來完善敌买。
如果你不想攔截那么深層次的話,vue3也提供了一個shallowRef阶界,他就只攔截value的變化了虹钮,value再往深了去就不會被監(jiān)聽到了。
function shallowRef(target) {
return{
_value:target,
get value(){
console.log('劫持到了get')
return this._value
},
set value(val){
console.log('劫持到了set',val)
this._value = val
}
}
}
以上就是本文全部內(nèi)容膘融。僅供參考學習