简介
??Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。Proxy可以理解成:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
let proxy = new Proxy(target, handler);
??ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。其中,new Proxy() 表示生成一个Proxy实例;target 参数表示所要代理(拦截)的目标对象,即如果没有Proxy的介入,操作原来要访问的就是这个对象;handler 参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截(代理)对应的操作;如果handler没有设置任何拦截,那就等同于直接操作原对象,且一个拦截器函数中可以设置拦截多个操作。注意:要使得Proxy起作用,必须针对Proxy实例 进行操作,而不是针对目标对象 进行操作。
let person = { name: '柳小姐', age: 18 };
let p = new Proxy(person, {
get(target,propName) {
console.log(`读取了p身上的${propName}属性`);
return target[propName];
}
set(target,propName,value) {
console.log(`修改了p身上的${propName}属性`);
target[propName] = value;
}
});
另外,Proxy实例也可以作为其他对象的原型对象。
let proxy = new Proxy({}, {
get(target,propName) {
return 35;
}
});
let obj = Object.create(proxy);
console.log(obj.time);
注意:如果一个属性不可配置(configurable) 且不可写(writable) ,则 Proxy不能 修改该属性,否则通过 Proxy 对象访问该属性会报错。
Proxy实例的方法
1、get(target, propKey, receiver): :get方法用于拦截对象属性的读取操作 ,如 proxy.foo 和 proxy[‘foo’]。可以接受三个参数:依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。另外,get 方法可以继承。
let p = new Proxy({}, {
get(target, propertyKey) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(p);
console.log(obj.foo);
2、set(target, propKey, value, receiver) :set方法用来拦截对象属性的设置(如增加、修改等)操作 ,如 proxy.foo = v 或 proxy[‘foo’] = v。可以接受四个参数:依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。且set 代理应当返回一个布尔值。严格模式下,set代理如果没有返回 true ,就会报错。 ??下面例子中set方法的第四个参数,指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身:
let p = new Proxy({}, {
set(obj, prop, value, receiver) {
obj[prop] = receiver;
return true;
}
});
let myObj = {};
Object.setPrototypeOf(myObj, p);
myObj.foo = 'bar';
console.log(myObj.foo === myObj);
注意:如果目标对象自身的某个属性不可写(writable) ,那么set方法将不起作用!
3、apply(target, object, args) :apply方法拦截函数的调用 、call 和 apply 操作,如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。该方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this )和目标对象的参数数组。
let p = new Proxy(function sum(left,right) {
return left + right;
},{
apply(target,object,args) {
return Reflect.apply(...arguments) * 2;
}
});
console.log(p(1,2));;
console.log(p.call(null,3,4));;
console.log(p.apply(null,[5,6]));;
Reflect.apply(proxy, null, [9, 10]);
4、isExtensible(target) :拦截 Object.isExtensible(proxy) ,该方法只能返回布尔值,否则返回值会被自动转为布尔值。且该方法的返回值必须与目标对象的 isExtensible 属性保持一致,否则就会抛出错误。
let p = new Proxy({}, {
isExtensible(target) {
return false;
}
});
Object.isExtensible(p);
5、deleteProperty(target, propKey) :拦截 delete 的操作,如果这个方法抛出错误 或者返回false ,当前属性就无法被delete命令删除。返回一个布尔值。
let handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
delete target[key];
return true;
}
};
function invariant(key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
let target = { _prop: 'foo' };
let p = new Proxy(target, handler);
delete p._prop;
注意:目标对象自身的不可配置(configurable) 的属性,不能被 deleteProperty 方法删除,否则报错!
6、ownKeys(target) :拦截 Object.getOwnPropertyNames(proxy) 、Object.getOwnPropertySymbols(proxy) 、Object.keys(proxy) 、for...in 循环的操作,返回一个数组,该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性(即不会返回目标对象上不存在的属性、属性名为symbol值 的属性以及不可遍历的属性)。
let target = { _bar: 'foo', _prop: 'bar', prop: 'baz' };
let p = new Proxy(target, {
ownKeys (target) {
return Reflect.ownKeys(target).filter(key => key[0] !== '_');
}
});
for (let key of Object.keys(p)) {
console.log(target[key]);
}
注意:1、ownKeys() 方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。 ???2、如果目标对象自身包含不可配置的属性 ,则该属性必须被 ownKeys() 方法返回,否则报错。 ???3、如果目标对象是不可扩展的(non-extensible) ,这时ownKeys() 方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。
7、getOwnPropertyDescriptor(target, propKey) :拦截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象或者undefined。
let target = { _foo: 'bar', baz: 'tar' };
let p = new Proxy(target, {
getOwnPropertyDescriptor(target, key) {
if (key[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, key);
}
});
console.log(Object.getOwnPropertyDescriptor(p, 'wat'));
console.log(Object.getOwnPropertyDescriptor(p, '_foo'));
console.log(Object.getOwnPropertyDescriptor(p, 'baz'));
8、defineProperty(target, propKey, propDesc) :拦截 Object.defineProperty(proxy, propKey, propDesc) 、Object.defineProperties(proxy, propDescs) ,==返回一个布尔值=。
let p = new Proxy({}, {
defineProperty(target, key, descriptor) {
return false;
}
});
p.foo = 'bar';
注意:如果目标对象不可扩展(non-extensible) ,则defineProperty()不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable) 或不可配置(configurable) ,则defineProperty()方法不得改变这两个设置。
9、preventExtensions(target) :用来拦截 Object.preventExtensions(proxy) 。该方法必须返回一个布尔值,否则会被自动转为布尔值。这个方法有一个限制,只有目标对象不可扩展时(即 Object.isExtensible(proxy)为false ),proxy.preventExtensions 才能返回true,否则会报错。为了防止出现这个问题,通常要在proxy.preventExtensions()方法里面,调用一次Object.preventExtensions()。 10、getPrototypeOf(target) :拦截获取对象原型的操作,如 Object.prototype.__proto__、Object.prototype.isPrototypeOf()、Object.getPrototypeOf()、Reflect.getPrototypeOf()、instanceof ,返回一个对象或者null,否则报错。
let proto = {};
let p = new Proxy({}, {
getPrototypeOf(target) {
return proto;
}
});
Object.getPrototypeOf(p) === proto;
注意:如果目标对象不可扩展(non-extensible) , getPrototypeOf() 方法必须返回目标对象的原型对象。
11、has(target, propKey) :拦截 HasProperty 操作,即判断对象是否具有某个属性(如 in 运算符)。该方法可以接受两个参数,分别是目标对象、需查询的属性名,返回一个布尔值。
let handler = {
has(target, prop) {
return false;
}
}
let obj = { a: 10 };
Object.preventExtensions(obj);
let p = new Proxy(obj, handler);
console.log('a' in p);
let obj1 = { b: 18 };
let p1 = new Proxy(obj1, handler);
console.log('b' in p1);
注意:has() 方法拦截的是HasProperty 操作,而不是HasOwnProperty 操作,即has()方法不判断一个属性是对象自身的属性,还是继承的属性。另外,虽然for…in循环用到了in运算符,但是has()拦截对for…in循环不 生效。
12、setPrototypeOf(target, proto) :拦截 Object.setPrototypeOf(proxy, proto) ,只能返回一个布尔值,否则会被自动转为布尔值。
注意:如果目标对象不可扩展(non-extensible) ,setPrototypeOf() 方法不得改变目标对象的原型。
13、construct(target, args) :拦截Proxy 实例作为构造函数调用的操作 ,比如new proxy(…args),返回的必须是一个对象,否则报错。construct()方法可以接受三个参数:第一个是目标对象target ,第二个是构成函数的参数数组args ,第三个是创造实例对象时new命令作用的构造函数。
注意:由于construct()拦截的是构造函数,所以它的目标对象必须是函数 ,否则就会报错。另外,construct()方法中的this 指向的是handler,而不是实例对象。
Proxy.revocable()
Proxy.revocable() 方法返回一个可取消的 Proxy 实例。Proxy.revocable() 方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo
revoke();
proxy.foo
使用场景
例如这种情况下可以使用该函数:目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
this问题
虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this 关键字会指向 Proxy 代理,Proxy 拦截函数内部的this 指向的是handler对象。
const target = {
m() {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m()
proxy.m()
参考:阮一峰——ES6入门
|