前言
Proxy对象是ES6(ECMAScript 6)中提供用来操作对象的一个API,其含义官方给出的解释是:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)
。
- 小白:额~,读了三遍,还是不太明白。
- 隔壁老王:
Proxy
本意是代理
,那既然代理,肯定要具备代理的俩基本素质,1.帮谁代理,2.干点什么。所以, 帮谁代理?帮目标对象代理
,干点什么?架设拦截,对外界的访问进行过滤和改写
,串起来说就是:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
。这下明白了吧,嘿嘿。 - 小白:喔育,不愧是隔壁老王,就是厉害~
语法
let proxy = new Proxy(target, handler);
tgarget
:要代理的目标对象。(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
:定义拦截行为的配置对象(也是一个对象,其内部的属性均为执行操作的函数)。
- proxy支持的拦截操作一共 13 种,即支持13种代理行为(函数),这些函数如果你需要复写其行为方式,均要定义在handler这个对象中使用,下面我们就针对这些支持的比较常用的操作函数进行举例说明。其余的不是很常用,如果想了解可以参考文末的资料去学习。
get(target, key, receiver)
含义:拦截对象属性的读取
target
:要代理的目标对象。
key
:属性名。
receiver
:proxy实例(可选参数,一般不用)
const proxy1 = new Proxy({
a: 1
}, {
get(target, key) {
if(Reflect.has(target,key)) {
return Reflect.get(target,key);
}else {
return false;
}
}
})
proxy1.a //1
proxy1.b //false
以上,a属性因为有定义,所以可获取到其值为1,b属性没有被定义,输出为false
,如果这里没有定义get方法,那么获取操作就会被转发到目标对象身上,默认如果没有此属性,会返回undefined
,而这里却返回false
,正是因为重新定义了get方法,对返回内容进行了修改,正如其含义,对代理对象属性的读取进行了拦截。
set(target, key, value, receiver)
含义:拦截对象属性的设置
target
:要代理的目标对象。
key
:要设置的属性名。
value
:要设置的属性值。
receiver
:proxy实例(可选参数,一般不用)
const handler = {
set(target,key,value) {
if(!Reflect.has(target,key)) {
Reflect.set(target,key,value);
}else {
console.log(`${key}属性已存在`) //a属性已存在
}
},
get(target,key) {
return Reflect.get(target,key);
}
}
const proxy = new Proxy({},handler);
proxy.a = 1;
console.log(proxy.a);//1
proxy.a = 2;
console.log(proxy.a) //1
以上,设置属性的时候如果当前属性已存在,则不能设置成功,因此,我们可以利用set方法来拦截设置符合我们期望的属性,根据应用场景,自由发挥。
var obj = {};
const target = Object.defineProperty(obj, 'a', {
value: 123,
writable: false,
});
const handler = {
set(target,key,value) {
Reflect.set(target,key,value);
},
get(target, key) {
return Reflect.get(target,key);
}
};
const proxy = new Proxy(target, handler);
proxy.a = 456;
console.log( proxy.a) //123
以上,当目标对象的某个属性为不可写状态,那么set方法将会失效。
- 看到很多资料都写到是当目标对象的属性为
不可写且不可配置
状态,set方法将会失效,但自测发现其结果与configurable
属性的状态并无关系,只与writable
属性有关,有点小疑惑,希望看到的大佬能帮小弟解惑。
has(target, Key)
含义:判断对象是否具有某个属性。
target
:要代理的目标对象。
key
:要设置的属性名。
当判断一个对象中是否具有某个属性时,has方法就会生效,典型的操作就是in运算符应用,返回值为布尔类型
const handler = {
has(target, key) {
if (key[0] === '_') {
console.log('it is a private property')
return false;
}
return key in target;
}
};
const target = {
_a: 123,
a: 456
};
const proxy = new Proxy(target, handler);
console.log('_a' in proxy) // it is a private property false
console.log('a' in proxy);//true
以上,当对proxy使用in
运算符时,就会自动触发has方法,如果判断当前属性以_开头的话,就进行拦截,从而不被后面的in
运算符发现,实现隐藏某些特定属性的目的。
不过需要注意,当目标对象是不可扩展或者对象的属性是不可配置时has方法不能隐藏目标对象的当前属性,否则拦截会报错。
var obj = {
a: 10
};
Object.preventExtensions(obj); //使obj对象不可扩展,也就是使其不能增加新的属性
var p = new Proxy(obj, {
has: function (target, key) {
return false;
}
});
'a' in p // Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
let obj = {};
Object.defineProperty(obj, "a", {
configurable: false, //当前属性不可配置
value: 10,
});
var p = new Proxy(obj, {
has: function (target, key) {
return false;
}
});
'a' in p // Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' which exists in the proxy target as non-configurable
apply(target, thisArgs, args)
含义:拦截函数的调用、call和apply操作
target
:目标对象。
thisArgs
:目标对象的上下文对象(this)。
args
:目标对象的参数数组。
const handler = {
apply(target, ctx, args) {
return Reflect.apply(...arguments) * 2;
//或者
return target(...args) * 2
}
};
function sum(...args) {
let num = 0;
args.forEach((item) => {
num += item;
})
return num;
}
var proxy = new Proxy(sum, handler);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
以上,目标对象这里是sum
,必须是一个函数,否则调用会报错。每当proxy函数被直接调用或者call
,apply
方式调用都会立即触发apply
方法,从而该调用被apply
方法拦截,这样就可以利用拦截修改返回值,如果不写apply方法,默认调用sum方法,返回 结果。
construct(target, args,newTarget)
含义:拦截new命令
target
:目标对象。
args
:构造函数的参数列表。
newTarget
:创建实例对象时,new命令作用的构造函数(下面例子的p)。
var p = new Proxy(function () {}, {
construct: function (target, args, newTarget) {
console.log('called: ' + args.join(', '));
return {
value: args[0] * 10
};
// return 1 //Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')
}
});
(new p(1)).value
// "called: 1"
// 10
以上,代理的目标对象必须是一个构造函数(只有构造函数才可以使用new操作符),当执行new p(1)
时,会立刻触发construct
函数,也就是会被该函数拦截,这里的返回值有讲究,必须返回对象,否则会报错。construct
方法执行完毕,p实例也就初始化完成了。
Proxy代理时 target 内部 this 的指向会发生改变
const target = {
foo: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.foo() // false
proxy.foo() // true
可以看出,当目标对象一旦被proxy
代理后,其内部this
就会指向proxy
,而非自身,因此需要注意这点。
结语
好啦,今天关于Proxy的介绍就到这了,以上也只是针对几个比较常用的操作函数进行了举例说明,如果想了解更多,建议参考以下资料,若有错误,还望各位大神指正!
阮一峰:ECMAScript 6 入门-Proxy
阮一峰:ECMAScript 6 入门-Reflect
Proxy - JavaScript | MDN