什么是 Proxy?
MDN 上是這么描述的——Proxy
對象用於定義基本操作的自定義行為(如屬性查找,賦值,枚舉,函數調用等)。
其實就是在對目標對象的操作之前提供了攔截,可以對外界的操作進行過濾和改寫,修改某些操作的默認行為,這樣我們可以不直接操作對象本身,而是通過操作對象的代理對象來間接來操作對象,達到預期的目的~
看一個例子:
let obj = { name:{name:'hhh'}, arr: ['吃','喝','玩'] } //proxy兼容性差 可以代理13種方法 get set //defineProperty 只對特定 的屬性進行攔截 let handler = { get (target,key) { //target就是obj key就是要取obj里面的哪個屬性 console.log('收集依賴') return target[key] }, set (target,key,value) { console.log('觸發更新') target[key] = value } } let proxy = new Proxy(obj,handler) //通過代理后的對象取值和設置值 proxy.arr proxy.name = '123'
定義了一個對象obj,通過代理后的對象(上面的proxy)來操作原對象。當取值的時候會走get方法,返回對應的值,當設置值的時候會走set方法,觸發更新。
但這是老的寫法,新的寫法是使用Reflect。
Reflect是內置對象,為操作對象而提供的新API,將Object對象的屬於語言內部的方法放到Reflect對象上,即從Reflect對象上拿Object對象內部方法。 如果出錯將返回false
簡單改寫上面這個例子
let handler = { get (target,key) { //target就是obj key就是要取obj里面的哪個屬性 console.log('收集依賴') // return target[key] //Reflect 反射 這個方法里面包含了很多api return Reflect.get(target,key) }, set (target,key,value) { console.log('觸發更新') // target[key] = value //這種寫法設置時如果不成功也不會報錯 比如這個對象默認不可配置 Reflect.set(target,key,value) } } let proxy = new Proxy(obj,handler) //通過代理后的對象取值和設置值 proxy.arr proxy.name = '123'
效果依舊和上面一樣。
但是有一個問題,這個對象是多層對象,它並不會取到里面的那個name的值。
這是因為之前Object.defineProperty方法是一開始就會對這個多層對象進行遞歸處理,所以可以拿到,而Proxy不會。它是懶代理。如果對這個對象里面的值進行代理就取不到值。就像上面我們只對name進行了代理,但並沒有對name.name進行代理,所以他就取不到這個值,需要代理之后才能取到。
let obj = {
name:{name:'hhh'},
arr: ['吃','喝','玩']
}
//proxy兼容性差 可以代理13種方法 get set
//defineProperty 只對特定 的屬性進行攔截
let handler = {
get (target,key) { //target就是obj key就是要取obj里面的哪個屬性
console.log('收集依賴')
if(typeof target[key] === 'object' && target[key] !== null){
//遞歸代理,只有取到對應值的時候才會代理
return new Proxy(target[key],handler)
}
// return target[key]
//Reflect 反射 這個方法里面包含了很多api
return Reflect.get(target,key)
},
set (target,key,value) {
console.log('觸發更新')
// target[key] = value //這種寫法設置時如果不成功也不會報錯 比如這個對象默認不可配置
Reflect.set(target,key,value)
}
}
let proxy = new Proxy(obj,handler)
可以看到這次雖然我寫了代理name.name,但是沒有取到name.name,它並不會真正的代理
這次觸發了兩次收集依賴。拿到了里面name的值。。
接下來看看數組的代理過程:
let obj = { name:{name:'hhh'}, arr: ['吃','喝','玩'] } //proxy兼容性差 可以代理13種方法 get set //defineProperty 只對特定 的屬性進行攔截 let handler = { get (target,key) { //target就是obj key就是要取obj里面的哪個屬性 console.log('收集依賴') if(typeof target[key] === 'object' && target[key] !== null){ //遞歸代理,只有取到對應值的時候才會代理 return new Proxy(target[key],handler) } // return target[key] //Reflect 反射 這個方法里面包含了很多api return Reflect.get(target,key) }, set (target,key,value) { console.log('觸發更新') // target[key] = value //這種寫法設置時如果不成功也不會報錯 比如這個對象默認不可配置 return Reflect.set(target,key,value) } } let proxy = new Proxy(obj,handler) //通過代理后的對象取值和設置值 // proxy.name.name = '123' //設置值,取一次,設置一次 proxy.arr.push(456)
這里面它會走兩次觸發更新的操作,因為第一次需要修改數組的長度,第二次再把元素放進數組里。所以我們需要判斷一下它是新增操作還是修改操作
set (target,key,value) { let oldValue = target[key] console.log(key, oldValue, value) if(!oldValue){ console.log('新增屬性') }else if(oldValue !== value){ console.log('修改屬性') } // target[key] = value //這種寫法設置時如果不成功也不會報錯 比如這個對象默認不可配置 return Reflect.set(target,key,value) }
首先拿到它的舊值,如果這個值不存在就是新增,如果存在但不相等就是修改操作
可以看到最后一次判斷結果是兩個相等,什么也不做
這是一次修改操作。
我們知道,vue2.0是不會監控到之前不存在的屬性的,但是proxy可以操作之前不存在的屬性的,它會攔截設置操作,如下:
xxx之前並不在obj對象里面,但是依舊可以新增。