Proxy 用来修改某些默认操作,等同于在语言层面做出修改。所以属于一种元编程(meta programming), 即对编程语言进行编程。字面理解为Proxy代理了某些默认的操作。
其使用格式如下:
var proxy = new Proxy(target, handler);
target是被代理的目标对象,handler也是个对象,用来定制拦截行为,内部定义每个被代理的行为。
注意:
看一个简单的实例
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
//被代理的对象无论输入什么属性都返回35
实际上,proxy 对象也可以被继承:
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对象中 get 方法表示属性的访问请求,set 方法表示属性的写入请求。
当然不仅仅 get 和 set, 我们可以定义以下拦截函数:
Object.keys(proxy)
不应该包括不可遍历属性以下列举一些 Proxy 的实例
访问对象不存在的属性报错
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
数组索引为负时返回倒数位置的值
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
保护对象内以 “_” 开头的属性为私有属性:
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
当然不是所有 proxy 代理都不可取消,下面方法设置的代理是可以通过定义代理时返回的revoke函数取消:
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 对象有一下作用:
1. 将 Object对象的一些明显属于语言层面的方法部署在 Reflect 上
2. 修改某些 Object 对象的方法使其更合理。比如 Object.defineProperty
遇到无法定义属性时会抛出错误,而 Reflect.defineProperty
会返回 false
3. 把所以 object 的操作都替换成函数行为,比如用 Reflect.has(obj,name)
替换 name in obj
4. 保证只要是 Proxy 有的方法就一定可以在 Reflect 上找到相同的方法,这样可以在实现 proxy 时方便的完成默认行为。换言之,无论 proxy 怎么修改默认行为,你总可以在 Reflect 上找到真正默认的行为
代理在添加额外的功能时,利用 Reflect 保证了原始功能的实现。举个例子:
var loggedObj = new Proxy({}, {
get(target,propKey){
console.log(`getting ${target}.${propKey}`); //当然你最好把操作记录到一个 log 中
return Reflect.get(target,propKey);
}
});
Reflect有以下方法:
ObjectgetOwnPropertyDescriptor(target, propKey)
Object.defineProperty(target,propKey,desc)
Object.getOwnPropertyNames(target)
Object.getPrototypeOf(target)
Object.setPrototypeOf(target, proto)
delete target.propKey
for ... in target
Object.freeze(target)
Object.seal(target)
Object.preventExtensions(target)
Object.isFrozen(target)
Object.isSealed(target)
Object.isExtensible(target)
propkey in object
target.hasOwnProperty(propKey)
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"
Function.prototype.apply.call(target, thisArg, args)
即 thisArg.target(args)
new target(...args)
注意以上方法中,Reflect.set()
, Reflect.defineProperty()
, Reflect.freeze()
, Reflect.seal()
, Reflect.preventExtensions()
在成功时返回 true, 失败时返回 false。对应的 Object 方法失败时会抛出错误。