概述
Proxy可以理解成在目标对象之前架设一层拦截,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。可以由它来代理某些操作。
- ES6提供原生
Proxy构造函数,用来生成Proxy实例。接受两个参数,参数一是要代理的目标对象;参数二是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。
let obj = {a: 30};
let proxy = new Proxy(obj, {
get(target, propKey){
return 35;
}
})
console.log(proxy.time);
console.log(obj.a);
实例方法
get()
- 用于拦截某个属性的读取操作,接受三个参数,依次为目标对象、属性名和
proxy实例本身(可选)。
let obj = {a: 30};
let proxy = new Proxy(obj, {
get(target, propKey){
return target[propKey];
}
})
console.log(proxy.a);
- 如果一个属性设置了
不可配置和不可写,则Proxy不能修改该属性,否则通过Proxy对象访问该属性会报错。
const obj = Object.defineProperty({}, 'foo', {
value: 10,
writable: false,
configurable: false
})
let proxy = new Proxy(obj, {
get(target, propKey) {
return 'abc';
}
})
console.log(proxy.foo);
set()
- 用来拦截某个属性的赋值操作,接受四个参数,依次为目标对象、属性名、属性值和
proxy实例本身(可选)。
- 如果目标对象的某个属性不可写,那么
set方法将不起作用。
let person = new Proxy({}, {
set(target, prop, value){
if('age' === prop){
if(!Number.isInteger(value)){
throw new TypeError('The age is not an Integer');
}
if(value > 200){
throw new RangeError('The age eems invalid');
}
target[prop] = value;
return true;
}
}
})
person.age = 100
apply()
- 拦截函数的调用、
call和apply操作。接受三个对象,依次是目标对象,目标对象的上下文对象(this)和目标对象的参数数组。
let fun = function (str) {
return str;
}
let proxy = new Proxy(fun, {
apply(target, context, args) {
console.log(target)
console.log(context)
console.log(args)
}
})
let obj = {}
proxy.call(obj, '123');
has()
- 用来拦截
HasProperty操作,即判断对象是否具有某个属性(自身的和继承的都算)时,这个方法会生效,典型的就是in操作符。接收两个参数,依次是目标对象、需要查询的属性名。
- 只对
in运算符有效,对for in循环不生效。
let obj = {
'_a': 1,
b: 2
}
Object.prototype.c = 3;
let proxy = new Proxy(obj, {
has(target, prop){
if(prop.startsWith('_')){
return false;
}
return prop in target;
}
})
'_a' in proxy
'b' in proxy
'c' in proxy
construct()
- 用于拦截
new命令。接收三个参数,依次是目标对象、构造函数的参数数组、proxy实例。返回的必须是一个对象,否则会报错。
function Person(name, age){
this.name = name;
this.age = age;
}
let personFactory = new Proxy(Person, {
construct(target, args, fn){
console.log(target)
console.log(args)
console.log(fn)
return new target(...args);
}
})
let person = new personFactory('张三', 20);
deleteProperty()
- 用于拦截
delete操作,如果此方法抛出错误或返回false,当前属性就无法被delete删除。两个参数,依次是目标对象,属性名。如果对象的属性的是不可配置的,则不能被删除。
let obj = Object.defineProperties({}, {
'_a': {
value: 1,
configurable: true
},
'b': {
value: 2,
configurable: false
},
'c': {
value: 3,
configurable: true
}
})
let p = new Proxy(obj, {
deleteProperty(target, prop){
if(prop.startsWith('_')){
return false;
}
return delete target[prop];
}
})
delete p['_a']
delete p.b
delete p.c
p
defineProperty()
- 拦截
Object.defineProperty操作。三个参数,依次为目标对象、属性名、属性描述对象。如果目标对象不可扩展,则不能在目标对象上增加新属性。如果目标对象的某个属性为不可写或不可配置,则修改依然不生效。
let obj = {};
let p = new Proxy(obj, {
defineProperty(target, key, descriptor) {
if (key[0] === '_') {
return target;
} else {
return Object.defineProperty(target, key, descriptor)
}
}
})
p.b = 2;
p
Object.defineProperty(p, 'a', { value: 1 })
Object.defineProperty(p, '_a', { value: 1})
getOwnPropertyDescriptor()
- 拦截
Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。两个参数,依次为目标对象,属性名。
let target = {
_foo: 'bar',
baz: 'tar'
};
let proxy = new Proxy(target, {
getOwnPropertyDescriptor(target, key) {
if (key[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, key);
}
});
Object.getOwnPropertyDescriptor(proxy, 'wat')
Object.getOwnPropertyDescriptor(proxy, '_foo')
Object.getOwnPropertyDescriptor(proxy, 'baz')
getPropertyOf()
- 拦截获取对象原型,包括以下方法:
__proto__、isPrototypeOf()、Object.getPrototypeOf()、Reflect.getPrototypeOf()、instanceof。
let proto = {};
let p = new Proxy(proto, {
getPrototypeOf(target){
return [];
}
});
console.log(Object.getPrototypeOf(p))
isExtensible()
- 拦截
Object.isExtensible()操作。只能返回boolean值。
- 这个方法有一个强限制,它的返回值必须与目标对象的
isExtensible属性保持一致,否则就会抛出错误。
let p = new Proxy({}, {
isExtensible: function(target) {
console.log("called");
return true;
}
});
Object.isExtensible(p)
ownKeys()
- 拦截对象自身属性的读取操作,拦截以下方法:
getOwnPropertyNames()、getOwnPropertySymbols()、Object.keys()、for in 循环。
ownKeys()方法只能返回数组,且返回的数组成员只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。
let target = {
a: 1,
b: 2,
c: 3
};
let handler = {
ownKeys(target) {
return ['a'];
}
};
let proxy = new Proxy(target, handler);
Object.keys(proxy)
preventExtensions()
- 拦截
Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。
- 这个方法有一个限制,只有目标对象不可扩展时(即
Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错。
let proxy = new Proxy({}, {
preventExtensions: function(target) {
return true;
}
});
Object.preventExtensions(proxy);
setPrototypeOf()
- 用来拦截
Object.setPrototypeOf()方法。只能返回布尔值。如果目标不可扩展,则此方法不得改变目标对象的原型。
let obj = {}
let obj2 = {a: 1};
let p = new Proxy(obj, {
setPrototypeOf(target, proto) {
console.log('change prototype')
Object.setPrototypeOf(target, proto);
return true;
}
})
Object.setPrototypeOf(p, obj2);
console.log(Object.getPrototypeOf(p) === obj2)
Proxy.revocable()
- 创建一个可撤销的
proxy实例,返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。
- 使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
let { proxy, revoke } = Proxy.revocable({a: 1 }, {
get() {
return '哈哈';
}
})
console.log(proxy.a);
revoke();
console.log(proxy.a);
this指向
- 在 Proxy 代理的情况下,目标对象内部的
this关键字会指向 Proxy 实例。
const target = {
m: function () {
console.log(this === target);
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m()
proxy.m()
- Proxy 拦截函数内部的
this,指向的是配置对象。
const handler = {
get: function (target, key, receiver) {
console.log(this === handler);
return 'Hello, ' + key;
},
set: function (target, key, value) {
console.log(this === handler);
target[key] = value;
return true;
}
};
const proxy = new Proxy({}, handler);
proxy.foo
proxy.foo = 1