打开 Chrome 控制台,输入 window.Proxy ,你会发现 JavaScript 已经内置了一个全局的 Proxy 对象,请问这个对象是做什么用的?其实你用关键词「Proxy MDN」搜索一下,就能得到一个详细的教程。(在关键词后面加 MDN 是一个前端必备的小技巧哦)我今天只做一个简单的介绍。
假设我们有一个数据(对象)data,内容为
var data = {
username: 'Frank',
age: 26
}
现在我们给 data 创建一个代理 proxy
var proxy = new Proxy(data, {set: function(){...}, get: function(){...} })
那么,「proxy 就全权代理 data了」,这话是什么意思呢?意思就是 data 放假去了,如果你有任何事情要找 data,直接找 proxy 就好了,proxy 现在是 data 的秘书、代理人。比如原本你如果要改 username,那么应该写 data.username = 'frank';那么现在你只需要写 proxy.username = 'frank' 就好了。原本你如果想写 console.log(data.username),现在也只需要 console.log(proxy.username) 就可以了。
那么我们思考一下这样做有什么意义呢?
意义就是你能监控每一次对 data 的读写操作。
proxy.username = 'frank' 这句话实际上会运行 set 方法。set 方法可以对传入的值进行监控和过滤。假设 PM 要求「username 前后不能含有空格」,用 Proxy 就很好实现这一需求,只需要把 set 写成这样:
set: function(obj, prop, value){
obj[prop] = value.trim()
}
再假设 PM 要求「统计 username 被读取的次数」,那么我们只需要把 get 写成这样:
get: function(obj, prop){
if(prop === 'username'){
count += 1
}
return obj[prop]
}
一、proxy对象是什么
Proxy 对象到底是什么呢?Proxy 的意思是代理,proxy对象的作用是:通过Proxy 创建1个代理对象,然后通过操作代理对象允许你对指定的对象的一些行为进行自定义处理。
Proxy(target,handler); Proxy构造函数接收2个对象,第1个参数就是要处理的对象,第2个参数就是要自定义处理的方法的合集(也就是个对象)。
很抽象?其实就和js中的Object.defineProperty很像。Object.defineProperty 定义访问器属性,可以对某个属性的读写行为进行控制,在Proxy中也可以做到,而且Proxy更灵活和强大,它能做到很多访问器属性做不到的事情。比如,监听属性删除事件(delete obj.prop;)、in 事件('id' in obj;)、apply调用等。
先来看看,proxy对象有哪些内容。
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小学'
}
var handler = {};
// 此处,我们先不对targetObj的行为进行干预,所以传个空对象进去即可。
var proxy = new Proxy(targetObj,handler);
console.log(proxy);
看看打印的proxy是什么。
可以看到,proxy对象中,包含了Handler属性和Target属性和IsRevoked,它们的值分别是我们传入的handler以及 targetObj和false。
这个isRevoked表示是否可撤销,生成可撤销的proxy对象用Proxy.revocable()方法,具体可去MDN查看文档。
二、通过Proxy 对象操作原对象
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小学'
}
var handler = {};
// 此处,我们先不对targetObj的行为进行干预,所以传个空对象进去即可。
var proxy = new Proxy(targetObj,handler)
/**
* 1、读取及修改属性,可以看到原来的对象的属性也被修改了
*/
console.log(proxy.age); // 20
console.log(targetObj.age); // 20
proxy.age = 22;
console.log(proxy.age); // 22
console.log(targetObj.age); // 22
/**
* 2、删除proxy对象的属性,影响原来的对象的属性
*/
console.log(proxy.school); // 小学
console.log(targetObj.school); // 小学
delete proxy.age;
console.log(proxy.age); // undefined
console.log(targetObj.age); // undefined
三、set方法和get方法
Proxy支持拦截的操作,一共有13种:
- get(target, propKey, receiver):拦截对象属性的读取,比如
proxy.foo
和proxy['foo']
。 - set(target, propKey, value, receiver):拦截对象属性的设置,比如
proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。 - 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
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 - 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)
。
先来干预get行为(属性读取行为)
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小学'
}
var handler = {
// 定义get方法,get方法可以接收2个参数,分别是原来的对象及属性
get : function(target,prop){
console.log(`${prop}属性正在被查看`);
console.log(targetObj == target); // true
return target[prop];
}
};
var proxy = new Proxy(targetObj,handler);
console.log(proxy.id);
/**
* 可以看到,打印顺序为:
* id属性正在被查看
* true
* 1
*/
接下来把某些属性变为 “私有” ,如不允许读取id属性。定义set方法,不允许修改id,name,age属性
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小学'
}
var handler = {
// 定义get方法,get方法可以接收2个参数,分别是原来的对象及属性
get : function(target,prop){
if(prop == 'id'){
return undefined;
}
return target[prop];
},
// 定义set方法,set方法比get多1个参数,那就是该属性修改时的值
set : function(target,prop,value){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允许修改${prop}属性`)
}else{
target[prop] = value;
}
}
};
var proxy = new Proxy(targetObj,handler);
/**
* 修改属性,分别打印
* 不允许修改id属性
* 不允许修改name属性
* 不允许修改age属性
*/
proxy.id = 2;
proxy.name = 'pxh222';
proxy.age = 23;
proxy.school = '中学'; // 这个无打印
/**
* 读取属性,可以看到分别打印
* undefined
* pxh
* 20
* 中学 // 这个没有拦截,因此可以修改
*/
console.log(proxy.id);
console.log(proxy.name);
console.log(proxy.age);
console.log(proxy.school);
同样的,我们对删除对象属性的行为进行干预,不允许删除id,name,age属性。
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小学'
}
var handler = {
// 在handler中定义get方法,get方法可以接收2个参数,分别是原来的对象及属性
get : function(target,prop){
if(prop == 'id'){
return undefined;
}
return target[prop];
},
// set方法比get多1个参数,那就是该属性修改时的值
set : function(target,prop,value){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允许修改${prop}属性`)
}else{
target[prop] = value;
}
},
/**
* 这个方法要求返回个boolean值,表示是否删除成功
* 如果返回的值不是boolean值,则会进行类型转换成boolean值再返回
*/
deleteProperty : function(target,prop){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允许删除${prop}属性`);
return false;
}else{
delete target[prop];
return true;
}
}
};
var proxy = new Proxy(targetObj,handler);
/**
* 尝试删除id属性,可以看到打印顺序为:
* 不允许删除id属性
* false
*/
console.log(delete proxy.id);
/**
* 删除school属性,可以看到打印
* true
* undefined
*/
console.log(delete proxy.school);
console.log(proxy.school);
上面我们不允许获取对象的id值,也不可以修改和删除,现在我们把它隐藏掉
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小学'
}
var handler = {
// 在handler中定义get方法,get方法可以接收2个参数,分别是原来的对象及属性
get : function(target,prop){
if(prop == 'id'){
return undefined;
}
return target[prop];
},
// set方法比get多1个参数,那就是该属性修改时的值
set : function(target,prop,value){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允许修改${prop}属性`)
}else{
target[prop] = value;
}
},
/**
* 这个方法要求返回个boolean值,表示是否删除成功
* 如果返回的值不是boolean值,则会进行类型转换成boolean值再返回
*/
deleteProperty : function(target,prop){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允许删除${prop}属性`);
return false;
}else{
delete target[prop];
return true;
}
},
/**
* 通过has 方法来控制,返回值也是个boolean,表示对象是否拥有某个属性
* 如果返回的值不是boolean值,则会进行类型转换成boolean值再返回
*/
has : function(target,prop){
if(prop == 'id'){
return false
}else{
return prop in target;
}
}
};
var proxy = new Proxy(targetObj,handler);
console.log('id' in proxy); // false
console.log('name' in proxy); // true
更多实例用法,可以参考这篇博客:es6 Proxy对象详解
同样的方法,proxy还能干预对象的行为还有很多,感兴趣可以看MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy