Proxy 對(duì)象
Proxy 用來修改某些默認(rèn)操作赛蔫,等同于在語言層面做出修改。所以屬于一種元編程(meta programming), 即對(duì)編程語言進(jìn)行編程逛钻。字面理解為Proxy代理了某些默認(rèn)的操作。
其使用格式如下:
var proxy = new Proxy(target, handler);
target是被代理的目標(biāo)對(duì)象,handler也是個(gè)對(duì)象马绝,用來定制攔截行為,內(nèi)部定義每個(gè)被代理的行為挣菲。
注意:
- 如果希望這個(gè)代理有效富稻,需要在 proxy 對(duì)象上調(diào)用屬性方法,而不是在 target 上調(diào)用
- 如果指定 handler 為空對(duì)象白胀,那么得到對(duì)象和原對(duì)象一樣
- 得到的 proxy 是 target 的引用椭赋,如果沒有代理,在 proxy 上的修改和在 target 上的修改等同
看一個(gè)簡單的實(shí)例
var proxy = new Proxy({},{
get: function(target, key){
return 35;
}
});
console.log(proxy.time); //35
console.log(proxy.name); //35
console.log(proxy.title); //35
//被代理的對(duì)象無論輸入什么屬性都返回35
實(shí)際上或杠,proxy 對(duì)象也可以被繼承:
var proxy = new Proxy({},{
get: function(target, key){
return 35;
}
});
var obj = Object.create(proxy);
obj.time = 20;
console.log(obj.time); //20
console.log(obj.name); //35
感受一下它的威力:
var obj = new Proxy({}, {
get: function(target, key, receiver){
console.log(`getting ${key} ...`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver){
console.log(`setting ${key} ...`);
return Reflect.set(target, key, value, receiver);
}
});
obj.count = 1; //setting count ...
++obj.count; //getting count ...
//setting count ...
console.log(obj.count); //getting count ...
//2
可以看出來哪怔,handler對(duì)象中 get 方法表示屬性的訪問請(qǐng)求,set 方法表示屬性的寫入請(qǐng)求向抢。
當(dāng)然不僅僅 get 和 set蔓涧, 我們可以定義以下攔截函數(shù):
- get(target, propKey, receiver = target)
攔截對(duì)象的讀取屬性。當(dāng) target 對(duì)象設(shè)置了 propKey 屬性的 get 函數(shù)時(shí)笋额,receiver 綁定 get 函數(shù)的 this元暴。返回值任意 - set(target, propKey, value, receiver = target)
攔截對(duì)象的寫入屬性。返回一個(gè)布爾值 - has(target, propKey)
攔截 propKey in proxy 操作符兄猩,返回一個(gè)布爾值 - deleteProperty(target, propKey)
攔截 delete proxy[propKey] 操作符茉盏,返回一個(gè)布爾值 - enumerate(target)
攔截 for(let i in proxy) 遍歷器,返回一個(gè)遍歷器 - hasOwn(target, propKey)
攔截 proxy.hasOwnProperty('foo')枢冤,返回一個(gè)布爾值 - ownKeys(target)
攔截 Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy)鸠姨,返回一個(gè)數(shù)組。該方法返回對(duì)象所有自身屬性淹真,包括不可遍歷屬性讶迁,不包括 Symble屬性,但是Object.keys(proxy)
不應(yīng)該包括不可遍歷屬性 - getOwnPropertyDescriptor(target, propKey)
攔截 Object.getOwnPropertyDescriptor(proxy, propKey)核蘸,返回其屬性描述符 - defineProperty(target, propKey, propDesc)
攔截 Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDesc)巍糯,返回一個(gè)布爾值 - preventExtensions(target)
攔截 Object.preventExtensions(proxy)啸驯,返回一個(gè)布爾值 - getPrototypeOf(target)
攔截 Object.getPrototypeOf(proxy),返回一個(gè)對(duì)象 - isExtensible(target)
攔截 Object.isExtensible(proxy)祟峦,返回一個(gè)布爾值 - setPrototypeOf(target, proto)
攔截 Object.setPrototypeOf(proxy, proto)罚斗,返回一個(gè)布爾值 - apply(target, object, args)
攔截對(duì) proxy 實(shí)例的函數(shù)操作,包括 proxy(...args),proxy.call(object, ...args),proxy.apply(object, args) - construct(target, args, proxy)
攔截用 new 調(diào)用 proxy 函數(shù)的操作宅楞,construct()返回的不是對(duì)象會(huì)報(bào)錯(cuò)
以下列舉一些 Proxy 的實(shí)例
訪問對(duì)象不存在的屬性報(bào)錯(cuò)
var obj = new Proxy({}, {
get: function(target, key){
if(key in target){
return Reflect.get(target, key);
} else {
throw new ReferenceError(`"${key}" is not in object`);
}
}
});
obj.look = "picture";
console.log(obj.look); //"picture"
console.log(obj.sleep); //ReferenceError: "sleep" is not in object
數(shù)組索引為負(fù)時(shí)返回倒數(shù)位置的值
var origin = [10,20];
var arr = new Proxy(origin, {
get(target, key){
let index = parseInt(key);
if(index < 0){
index = target.length + index;
if(index < 0) return undefined;
}
return Reflect.get(target, index);
}
});
console.log(arr[0]); //10
console.log(arr[1]); //20
console.log(arr[2]); //undefined
console.log(arr[-1]); //20
console.log(arr[-4]); //undefined
保護(hù)對(duì)象內(nèi)以 "_" 開頭的屬性為私有屬性:
var o = {
"_name": "Bob",
"age": 13,
"_fun": function(){
console.log("_fun is called");
}
};
var obj = new Proxy(o, {
get(target, key){
if(key.charAt(0) === '_'){
return undefined;
}
return Reflect.get(target, key);
},
set(target, key, value){
if(key.charAt(0) === '_'){
throw new Error('Cannot define a property begin with "_"');
}
return Reflect.set(target, key, value);
},
has(target,key){
if(key.charAt(0) === '_'){
return false;
}
return Reflect.has(target, key);
},
deleteProperty(target,key){
if(key.charAt(0) === '_'){
return false;
} else {
Reflect.deleteProperty(..arguments);
}
},
apply(target,ctx,args){
if(target.name.charAt(0) === '_'){
throw new TypeError(`${target.name} is not defined`);
} else {
Reflect apply(...arguments);
}
},
defineProperty(target,key,desc){
if(key.charAt(0) === '_'){
return new Error(`cannot define property begin with "_"`);
} else {
Reflect.defineProperty(..arguments);
}
},
setPrototypeOf(target,proto){
throw new TypeError(`Cannot change the proto of ${target}`);
},
construct(target,ctx,args){
if(target.name.charAt(0) === '_'){
throw new TypeError(`${target.name} is not defined`);
} else {
Reflect construct(...arguments);
}
}
});
console.log(obj.age); //13
obj.age = 20;
console.log(obj.age); //20
console.log(obj._name); //undefined
obj._hobby = "Coding"; //Error: Cannot define a property begin with "_"
_name in key //false
delete obj._name;
Object.defineProperty(obj,"_hobby",{
value: "Coding"
});
Object.defineProperties(obj,{
'_hobby': {
value: "Coding"
}
});
obj._fun();
var a = new obj._fun();
obj.__proto__ = {}; //Cannot define a property begin with "_"
Object.setPrototypeOf(obj,{}) //Cannot change the proto of obj
當(dāng)然不是所有 proxy 代理都不可取消针姿,下面方法設(shè)置的代理是可以通過定義代理時(shí)返回的revoke函數(shù)取消:
var a = {
name:"Bob"
};
var {proxy, revoke} = Proxy.revocable(a, {
get(target,key){
return undefined;
}
});
proxy.name; //undefined;
revoke();
proxy.name; //TypeError: Cannot perform 'get' on a proxy that has been revoked
Reflect 對(duì)象
Reflect 對(duì)象有一下作用:
- 將 Object對(duì)象的一些明顯屬于語言層面的方法部署在 Reflect 上
- 修改某些 Object 對(duì)象的方法使其更合理。比如
Object.defineProperty
遇到無法定義屬性時(shí)會(huì)拋出錯(cuò)誤厌衙,而Reflect.defineProperty
會(huì)返回 false - 把所以 object 的操作都替換成函數(shù)行為距淫,比如用
Reflect.has(obj,name)
替換name in obj
- 保證只要是 Proxy 有的方法就一定可以在 Reflect 上找到相同的方法,這樣可以在實(shí)現(xiàn) proxy 時(shí)方便的完成默認(rèn)行為婶希。換言之榕暇,無論 proxy 怎么修改默認(rèn)行為,你總可以在 Reflect 上找到真正默認(rèn)的行為
代理在添加額外的功能時(shí)饲趋,利用 Reflect 保證了原始功能的實(shí)現(xiàn)拐揭。舉個(gè)例子:
var loggedObj = new Proxy({}, {
get(target,propKey){
console.log(`getting ${target}.${propKey}`); //當(dāng)然你最好把操作記錄到一個(gè) log 中
return Reflect.get(target,propKey);
}
});
Reflect有以下方法:
- Reflect.getOwnPropertyDescriptor(target, propKey)
等同于ObjectgetOwnPropertyDescriptor(target, propKey)
- Reflect.defineProperty(target,propKey,desc)
等同于Object.defineProperty(target,propKey,desc)
- Reflect.getOwnPropertyNames(target)
等同于Object.getOwnPropertyNames(target)
- Reflect.getPrototypeOf(target)
等同于Object.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, proto)
等同于Object.setPrototypeOf(target, proto)
- Reflect.deleteProperty(target, propKey)
等同于delete target.propKey
- Reflect.enumerate(target)
等同于for ... in target
- Reflect.freeze(target)
等同于Object.freeze(target)
- Reflect.seal(target)
等同于Object.seal(target)
- Reflect.preventExtensions(target)
等同于Object.preventExtensions(target)
- Reflect.isFrozen(target)
等同于Object.isFrozen(target)
- Reflect.isSealed(target)
等同于Object.isSealed(target)
- Reflect.isExtensible(target)
等同于Object.isExtensible(target)
- Reflect.has(target, propKey)
等同于propkey in object
- Reflect.hasOwn(target, propKey)
等同于target.hasOwnProperty(propKey)
- Reflect.ownKeys(target)
遍歷得到target自身所有屬性,包括不可枚舉屬性奕塑,不包括 Symbol 屬性 - Reflect.get(target,propKey, receiver = target)
如果 propKey 是個(gè)讀取器堂污,則讀取器中的 this 綁定到 receiver
var per = {
bar: function(){console.log("per-bar")}
}
var obj = {
get foo(){ this.bar(); },
bar: function (){console.log("obj-bar")}
};
Reflect.get(obj, "foo", per); //"per-bar"
- Reflect.set(target,propKey, value, receiver = target)
如果 propKey 是個(gè)讀取器,則讀取器中的 this 綁定到 receiver - Reflect.apply(target, thisArg, args)
等同于Function.prototype.apply.call(target, thisArg, args)
即thisArg.target(args)
- Reflect.construct(target,args)
等同于new target(...args)
注意以上方法中龄砰,Reflect.set()
, Reflect.defineProperty()
, Reflect.freeze()
, Reflect.seal()
, Reflect.preventExtensions()
在成功時(shí)返回 true, 失敗時(shí)返回 false盟猖。對(duì)應(yīng)的 Object 方法失敗時(shí)會(huì)拋出錯(cuò)誤。