概述
Proxy 可以理解成在目标对象前架设一个“拦截”层,外界对该对象的访问都必须先通过这层拦截层,即提供了一种机制可以对外界的访问进行过滤和改写。
ES6 提供 Proxy 构造函数,用于生成 Proxy 实例。
var proxy = new Proxy(target, handler);
其中,target 参数表示所要拦截的目标对象,handler 参数也是一个对象,定义拦截行为,如果没有设置任何拦截,那就等同于直接通向原对象。
var proxy = new Proxy({}, {
// get方法的两个参数分别是目标对象和所要访问的属性
get: function(target, property) {
return 35;
}
});
Proxy 实例的方法
get()
get 方法用于拦截某个属性的读取操作。
get 方法可以继承,即拦截操作定义在原型对象上时,读取目标对象继承的属性时,拦截会生效。
let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.xxx // "GET xxx"
set()
set 方法用于拦截某个属性的赋值操作。
如果目标对象的某个属性不可写也不可配置,那么 set 不得改变这个属性的值,只能返回同样的值,否则会报错。
apply()
apply 方法拦截函数的调用、call 和 apply 操作。接收3个参数,目标对象、目标对象的上下文对象(this)、目标对象的参数数组。
直接调用 Reflect.apply 方法也会被拦截。
let twice = {
apply(target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum (left, right) {
return left + right;
};
let proxy = new Proxy(sum, twice);
proxy(1, 2); // 6
proxy(null, 5, 6); // 22
proxy.apply(null, [7, 8]); // 30
Reflect.apply(proxy, null, [9, 10]); // 38
has()
has 方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效,典型的操作就是 in 操作符,但是拦截对 for... in 循环不生效。
let handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
let target = { _prop: 'foo', prop: 'foo' };
let proxy = new Proxy(target, handler);
'prop' in proxy; // true
'_prop' in proxy; // false
for (let a in proxy) {
console.log(proxy[a]); // true, true
}
如果原对象不可配置或者禁止扩展,那么这是 has 拦截会报错。
let obj = { a: 10 };
Object.preventExtensions(obj);
let p = new Proxy(obj, {
has: function(target, prop) {
return false;
}
});
'a' in p; // TypeError
construct()
construct 方法用于拦截 new 命令,接收两个参数:目标对象、构建函数的参数对象。 construct 方法返回的必须是一个对象,否则会报错。
let p = new Proxy(function () {}, {
construct: function(target, args) {
console.log('called:' + args.join(', '));
return { value: args[0] * 10 };
}
});
(new p(1)).value;
// 'called: 1'
// 10
deleteProperty()
deleteProperty 方法用于拦截 delete 操作,如果这个方法跑出错误或者返回 false,当前属性就无法被 delete 命令删除。
目标对象自身的不可配置属性不能被 deleteProperty 方法删除,否则会报错。
defineProperty()
defineProperty 方法拦截了 Object.defineProperty 操作,如果这个方法返回 false,当前属性就无法定义。
let handler = {
defineProperty (target, key, descriptor) {
return false;
}
};
let target = {};
let proxy = new Proxy(target, handler);
proxy.foo = 'bar';
getOwnPropertyDescriptor()
getOwnPropertyDescriptor 方法拦截 Object.getOwnPropertyDescriptor() ,返回一个属性描述对象或者 undefined。
getOwnPropertyDescriptor 方法返回值必须是对象或者 null,否则会报错。如果目标对象不可扩展,getOwnPropertyDescriptor 方法必须返回目标对象的原型对象。
var handler = {
getOwnPropertyDescriptor (target, key) {
if (key[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, key);
}
};
var target = { _prop: 'bar', baz: 'tar' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat'); // undefined
Object.getOwnPropertyDescriptor(proxy, '_prop'); // undefined
Object.getOwnPropertyDescriptor(proxy, 'baz'); // { value: 'tar', writable: true, enumerable: true, configurable: true }
isExtensible()
isExtensible 方法拦截 Object.isExtensible 操作。该方法只能返回布尔值,否则返回值会被自动转为布尔值,并且返回值必须与目标对象的 isExtensible 属性保持一致,否则会报错。
var p = new Proxy({}, {
isExtensible: function(target) {
console.log('callef');
return true;
}
});
Object.isExtensible(p);
// 'called'
// true
var p1 = new Proxy ({}, {
isExtensible(target) {
return false;
}
})
Object.isExtensible(p1); // 报错
ownKeys()
ownKeys 方法用来拦截对象自身属性的读取操作。拦截以下操作:
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
返回值:
- ownKeys 方法返回的数组成员只能是字符串或 Symbol 值
- 如果有其他类型的值,或者返回的根本不是数组,就会报错
- 如果目标对象包含不可配置的属性,则该属性必须被 ownKeys 方法返回,否则会报错
- 如果目标对象是不可扩展的,这时 ownKeys 方法返回的数组之中必须包含原对象的所有属性,且不能包含多余的属性,否则会报错
preventExtensions()
preventExtensions 方法拦截 Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转换为布尔值。
只有目标对象为不可扩展时,即 Object.isExtensible (proxy)为false,proxy.preventExtensions 才能返回 true,否则会报错。
setPrototypeOf()
setPrototypeOf 方法主要用于拦截 Object.setPrototypeOf 方法。
该方法只能返回布尔值,否则会被自动转换为布尔值。如果目标对象不可扩展,setPrototypeOf 方法不得改变目标对象的原型。
Proxy.revocable()
Proxy.revocable() 方法返回一个可取消的 Proxy 实例。该实例的 proxy 属性是 Proxy 实例,revoke 属性是一个函数,可以取消Proxy 实例。
let target = {};
let handler = {};
let { proxy, revoke } = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
this 问题
在 Proxy 代理的情况下,目标对象内部的 this 关键字会指向 Proxy 代理。有些原生对象的内部属性只有通过正确的 this 才能获取,所以 Proxy 也无法代理这些原生对象的属性,解决方法是 this 绑定原始对象。
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);
proxy.getDate(); // TypeError: this is not a Date object
const target1 = new Date('2021-4-11');
const handler1 = {
get(target, prop) {
if (prop === "getDate") {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
}
const proxy1 = new Proxy(target1, handler1);
proxy.getDate(); // 11