一、概述
Proxy
從字面意思可以理解爲 “代理”,用於修改某些操作的默認行爲,等同於在語言層面做出修改。通過代理可以在目標對象之前架設一層 “攔截” ,外界對該對象的訪問都必須通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
var obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
上面的這段代碼對空對象進行了一層攔截,重新定義了屬性的讀取和設置行爲。
Proxy
是一個構造函數,用於實例化實例。
let proxy = new Proxy(target,handler);
其中,target
就是攔截的目標,handler
用於控制攔截行爲。具體支持13種攔截行爲:
get(target,prop,receiver)
—— 攔截對象屬性的讀取set(target, propKey, value, receiver)
—— 攔截對象的設置has(target, propKey)
—— 攔截propKey in proxy
的操作deleteProperty(target, propKey)
—— 攔截delete proxy[propKey]
的操作ownKeys(target)
—— 攔截Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循環getOwnPropertyDescriptor(target, propKey)
—— 攔截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回屬性的描述對象。defineProperty(target, propKey, propDesc)
—— 攔截Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
preventExtensions(target)
—— 攔截Object.preventExtensions(proxy)
getPrototypeOf(target)
—— 攔截Object.getPrototypeOf(proxy)
isExtensible(target)
—— 攔截Object.isExtensible(proxy)
setPrototypeOf(target, proto)
—— 攔截Object.setPrototypeOf(proxy, proto)
apply(target, object, args)
—— 攔截Proxy
實例作爲函數調用的操作,比如proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
construct(target, args)
—— 攔截Proxy
實例作爲構造函數調用的操作,比如new proxy(...args)
二、實例方法
1.get()
用於攔截某個屬性的讀取操作,可以接受三個參數,分別是:目標對象、屬性名和 proxy
實例本身。
let person = {name:"jonas"}
let proxy = new Proxy(person,{
get(target, p, receiver) {
if(p in target)
return target[p]
throw new Error("p is not in target")
}
})
proxy.name //jonas
proxy.age //拋出錯誤
你會發現通過這段代碼改變了 JS
原生的編程能力。默認情況下訪問一個對象的不存在的屬性,結果是 undefined
,然而上面的代碼訪問了一個不存在的屬性 age
,結果拋出了我們自定義的異常。從而達到了修改編程能力的效果。
除了直接攔截目標對象以外,還可以直接攔截原型對象:
let proxy = new Proxy({},{
get(target, p, receiver) {
if(p in target)
return target[p]
throw new Error("p is not in target")
}
})
let obj = Object.create(proxy)
obj.name = "jonas"
console.log(obj.name) //jonas
console.log(obj.age) //報錯
實現數組負數索引:
let arr = [1,2,3,4,5]
let proxy = new Proxy(arr,{
get(target, p, receiver) {
let index = Number(p)
if(index >= 0 && index in target){
return target[index]
}else if(index < 0 && Math.abs(index) <= target.length){
return target[target.length + index]
}
throw new Error("p is not in target")
}
})
console.log(proxy[-2]) //4
console.log(proxy[2]) //3
2.set()
用於攔截某個屬性的賦值操作,可以接受四個參數,分別是:目標對象、屬性名、屬性值和 proxy
本身。
如果通過 obj.xxx
或者 obj[xxx]
的方式給一個對象添加屬性或者設置屬性時,我們無法對屬性值進行校驗過濾,然而通過該方法就可以實現這個需求了。
let proxy = new Proxy({},{
get(target,p){
if(p in target)
return target[p]
throw new Error("p is not in target")
},
set(target,key,value){
if(key === "age"){
if(Object.prototype.toString.call(value).slice(8,-1) !== "Number")
throw new Error("age is not a number")
if(value > 200 || value < 0)
throw new Error("you are not a man")
}
target[key] = value
}
})
proxy.age = 88
console.log(proxy.age) //88
上面這裏對 age
屬性的值進行了檢驗以及限制。
3.apply()
該方法可以攔截函數的調用、call()
和 apply()
該方法接受三個參數,分別是:目標對象、目標對象的上下文對象和目標對象的參數列表。
let target = function () {
console.log("target")
}
let proxy = new Proxy(target,{
apply(target, thisArg, argArray) {
console.log("proxy")
}
})
proxy() //proxy
4.has()
該方法用於攔截 hasProperty
操作,判斷對象是否具有某個屬性時,這個方法會生效。典型的案例就是 in
運算符。但對 for...in
不生效。
該方法可以接受兩個參數,分別是目標對象、屬性名。
5.construct()
攔截 new
命令。該方法可以接受三個參數,分別是:目標對象,構造函數的參數列表,構造函數。
該方法必須返回一個對象,否則會報錯。
6.deleteProperty()
用於攔截 delete
操作,如果這個方法拋出錯誤或者返回 false
,當前屬性就無法被刪除。