Proxy
一、概述
Proxy
用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(
meta programming),即对编程语言进行编程。
Proxy
可以理解成,在目标对象之前架设一层“拦截”
,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
代理对象构造函数:
var proxy = new Proxy(target, handler);
target是目标对象,handler定义拦截行为。
注意,要使得 Proxy 起作用,必须针对 Proxy 实例(上例是 proxy 对象)进行操作,而不是针对目标对象进行操作。
注意:如果handler是空对象,则相当于直接操作目标对象
var obj = {};
var proxy = new Proxy(obj,{});
proxy.name=1;
obj.name // 1 ,此时在obj对象上有了name
var o1= {}
var proxy2 = new Proxy(o1, {
get:function(target,prop) {
console.log("get")
},
set:function() {
console.log("set")
}
})
proxy2.age = 1; // set
console.log(o1.age) // undefined
// 定义了handler非空对象,则不会直接操作目标对象。实行了拦截。
对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
下面是 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) 。
二、proxy实例的方法
1. get()
get
方法用于拦截某个属性的读取
操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
var o1 = {}
let p1 = new Proxy(o1,{
get(target,prop,reciver){
console.log("getter");
console.log("target===o1",target===o1); // true
console.log("prop",prop); //name
console.log("reciver===p1",reciver===p1); // true
}
})
p1.name;
2.set()
set
方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
var o1 = {}
let p1 = new Proxy(o1,{
get(target,prop,reciver){
console.log("getter");
console.log("target===o1",target===o1); // true
console.log("prop",prop); //name
console.log("reciver===p1",reciver===p1); // true
},
set(target,prop,value,reciver){
console.log("setter");
console.log("target===o1",target===o1); // true
console.log("prop",prop);
console.log("value",value)
console.log("reciver===p1",reciver===p1); // true
target[prop] = value; // 这里需要赋值,如果不赋值,o1里面是没有age属性的
}
})
p1.age = 12;
注意,如果目标对象自身的某个属性,不可写且不可配置,那么 set 方法将不起作用。
var obj = {};
Object.defineProperty(obj,'foo',{
writable:false,
value:"hello", // set default value
enumerable:true
})
console.log(obj.foo) // hello
var proxy = new Proxy(obj,{
set(target,prop,value,reciver){
console.log("setter")
}
})
proxy.foo = 'world'; // setter
console.log(obj.foo) // hello 值没有改变
3.apply()
apply
方法拦截函数的调用、 call 和 apply 操作。
apply
方法可以接受三个参数,分别是目标对象
、目标对象的上下文对象
( this )和目标对象的参数数组
。
var obj = function(){
console.log("我是个函数")
}
var proxy = new Proxy(obj,{
apply(target,ctx,args){
console.log("apply");
console.log("target",target);
console.log("ctx",ctx);
console.log("args",args)
target(args)
}
})
var instance = {f:proxy}
instance.f()
4.has()
has
方法用来拦截HasProperty
操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in 运算符。
var obj = {name:"小红"};
var proxy = new Proxy(obj,{
has(target,prop){
console.log('has');
return prop in target;
}
})
console.log( 'name' in proxy); // has true
console.log(obj.hasOwnProperty('name')) //true 不拦截
for(let key in obj){ // 不拦截
console.log(key)
}
另外,虽然 for...in 循环也用到了 in 运算符,但是 has 拦截对 for...in 循环不生效。
5.construct()
construct
方法用于拦截new
命令,下面是拦截对象的写法。
var Person = function Person(name, age) {
this.name = name;
this.age = age;
}
var proxy = new Proxy(Person,{
construct(target,args,newTarget){
console.log(target);
console.log(args);
console.log(newTarget);
console.log(proxy === newTarget)
return {} // 必须返回一个对象
}
})
new proxy("hello",12)
三、Proxy.revocable()
Proxy.revocable()
方法返回一个可取消的 Proxy
实例。
revoke是一个函数,调用该函数后,取消proxy实例,再访问proxy实例就会抛出一个错误。
let obj = {name:"hello"};
let {proxy,revoke} = Proxy.revocable(obj,{})
proxy.age = 1; // 正常
revoke(); // 取消
proxy.age=2; // error Cannot perform 'set' on a proxy that has been revoked
四、this
Proxy 代理的情况下,目标对象内部的 this 关键字会指向 Proxy 代理。
let target = {
foo(){
console.log(this)
console.log(this === proxy)
}
}
let proxy = new Proxy(target,{ })
target.foo(); // false
proxy.foo(); // true
此外,有些原生对象的内部属性,只有通过正确的 this 才能拿到,所以 Proxy 也无法代理这些原生对象的属性。
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);
proxy.getDate(); // error