文章目录
一.简述
代理和反射是ES6新增的特性。可为开发者提供拦截和改变基本操作行为的能力。即给目标对象定义一个关联对象作为代理对象。这个代理对象可以作为抽象的目标对象来使用。
二.反射API
全局对象Reflect上定义了许多静态方法。包括了所有的捕获器函数和部分Object对象上的静态方法。这些Reflect对象上的捕获器函数与自定义的捕获器拦截方法具有同名称和函数标签,而且具备被拦截方法的默认行为。很多反射方法会返回称作"状态标记"的布尔值,表示意图执行的操作是否成功。
三.捕获器不变式
通过Object.getOwnPropertyDescriptor方法,可以获取到一个对象上某个属性的基本配置。基本配置属性如下:
//第一种
configurable: boolean
enumerable: boolean
value: any
writable: boolean
//第二种
configurable: boolean
enumerable: boolean
get: function || undefined
set: function || undefined
当在对代理对象进行基本操作时,也必须遵守这个属性基本配置的规则。假如这个属性是不可修改的,因此在set捕获器里面最终设置的值就必须与原本的值保持一致,否则会报错。
四.捕获器
使用代理最主要的目的就是定义捕获器。捕获器可以理解为对象"基本操作拦截器"。在代理对象上进行基本操作,会在基本操作传播到目标对象之前调用捕获器函数,捕获器函数的处理结果会最终作用到目标对象上。如果没有定义对应的捕获器,则相当于执行的基本操作的默认行为。
1.get捕获器
get()捕获器会在获取属性值的操作中被调用。对应的反射API方法为Reflect.get()。
(1)返回值
无限制
(2)处理程序参数
- target:目标对象。
- property:引用目标对象上的属性名称。
- receiver:代理对象或继承代理对象的对象。
(3)捕获器不变式
-
如果target.property不可写且不可配置,则处理程序返回的值必须与target.property匹配。
-
如果target.property不可配置且[[Get]]特性为undefined,处理程序的返回值也必须是undefined。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
get(target, property, receiver) {
console.log('get()');
return Reflect.get(...arguments)
}
});
proxy.foo;
// get()
2.set捕获器
set()捕获器会在设置属性值的操作中被调用。对应的反射API方法为Reflect.set()。
(1)返回值
返回true表示成功;返回false表示失败,严格模式下会抛出TypeError。
(2)处理程序参数
- target:目标对象。
- property:引用的目标对象上的字符串键属性。
- value:要赋给属性的值。
- receiver:代理对象或继承代理对象的对象。
(3)捕获器不变式
- 如果target.property不可写且不可配置,则不能修改目标属性的值。
- 如果target.property不可配置且[[Set]]特性为undefined,则不能修改目标属性的值。
- 在严格模式下,处理程序中返回false会抛出TypeError。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
set(target, property, value, receiver) {
console.log('set()');
return Reflect.set(...arguments)
}
});
proxy.foo = 'bar';
// set()
3.has捕获器
has()捕获器会在in操作符中被调用。对应的反射API方法为Reflect.has()。
(1)返回值
has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。
(2)处理程序参数
- target:目标对象。
- property:引用的目标对象上的字符串键属性。
(3)捕获器不变式
- 如果target.property存在且不可配置,则处理程序必须返回true。
- 如果target.property存在且目标对象不可扩展,则处理程序必须返回true。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
has(target, property) {
console.log('has()');
return Reflect.has(...arguments)
}
});
'foo' in proxy;
// has()
4.defineProperty捕获器
defineProperty()捕获器会在Object.defineProperty()中被调用。对应的反射API方法为Reflect.defineProperty()。
(1)返回值
defineProperty()必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。
(2)处理程序参数
- target:目标对象。
- property:引用的目标对象上的字符串键属性。
- descriptor:包含可选的enumerable、configurable、writable、value、get和set定义的对象。
(3)捕获器不变式
- 如果目标对象不可扩展,则无法定义属性。
- 如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。
- 如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
defineProperty(target, property, descriptor) {
console.log('defineProperty()');
return Reflect.defineProperty(...arguments)
}
});
Object.defineProperty(proxy, 'foo', { value: 'bar' });
5.getOwnPropertyDescriptor捕获器
getOwnPropertyDescriptor()捕获器会在Object.getOwnPropertyDescriptor()中被调用。对应的反射API方法为Reflect.getOwnPropertyDescriptor()。
(1)返回值
getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回undefined。
(2)处理程序参数
- target:目标对象。
- property:引用的目标对象上的字符串键属性。
(3)捕获器不变式
- 如果自有的target.property存在且不可配置,则处理程序必须返回一个表示该属性存在的对象。
- 如果自有的target.property存在且可配置,则处理程序必须返回表示该属性可配置的对象。
- 如果自有的target.property存在且target不可扩展,则处理程序必须返回一个表示该属性存在的对象。
- 如果target.property不存在且target不可扩展,则处理程序必须返回undefined表示该属性不存在。
- 如果target.property不存在,则处理程序不能返回表示该属性可配置的对象。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
getOwnPropertyDescriptor(target, property) {
console.log('getOwnPropertyDescriptor()');
return Reflect.getOwnPropertyDescriptor(...arguments)
}
});
Object.getOwnPropertyDescriptor(proxy, 'foo');
// getOwnPropertyDescriptor()
6.deleteProperty捕获器
deleteProperty()捕获器会在delete操作符中被调用。对应的反射API方法为Reflect.deleteProperty()。
(1)返回值
deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。
(2)处理程序参数
- target:目标对象。
- property:引用的目标对象上的字符串键属性。
(3)捕获器不变式
- 如果自有的target.property存在且不可配置,则处理程序不能删除这个属性。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
deleteProperty(target, property) {
console.log('deleteProperty()');
return Reflect.deleteProperty(...arguments)
}
});
delete proxy.foo
// deleteProperty()
7.ownKeys捕获器
ownKeys()捕获器会在Object.keys()及类似方法中被调用。对应的反射API方法为Reflect.ownKeys()。
(1)返回值
ownKeys()必须返回包含字符串或符号的可枚举对象。
(2)处理程序参数
- target:目标对象。
(3)捕获器不变式
- 返回的可枚举对象必须包含target的所有不可配置的自有属性。
- 如果target不可扩展,则返回可枚举对象必须准确地包含自有属性键。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
ownKeys(target) {
console.log('ownKeys()');
return Reflect.ownKeys(...arguments)
}
});
Object.keys(proxy);
// ownKeys()
8.getPrototypeOf捕获器
getPrototypeOf()捕获器会在Object.getPrototypeOf()中被调用。对应的反射API方法为Reflect.getPrototypeOf()。
(1)返回值
getPrototypeOf()必须返回对象或null。
(2)处理程序参数
- target:目标对象。
(3)捕获器不变式
- 如果target不可扩展,则Object.getPrototypeOf(proxy)唯一有效的返回值就是Object.getPrototypeOf(target)的返回值。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
getPrototypeOf(target) {
console.log('getPrototypeOf()');
return Reflect.getPrototypeOf(...arguments)
}
});
Object.getPrototypeOf(proxy);
// getPrototypeOf()
9.setPrototypeOf捕获器
setPrototypeOf()必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。
(1)返回值
setPrototypeOf()捕获器会在Object.setPrototypeOf()中被调用。对应的反射API方法为Reflect.setPrototypeOf()。
(2)处理程序参数
- target:目标对象。
- prototype:target的替代原型,如果是顶级原型则为null。
(3)捕获器不变式
- 如果target不可扩展,则唯一有效的prototype参数就是Object.getPrototypeOf(target)的返回值。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
setPrototypeOf(target, prototype) {
console.log('setPrototypeOf()');
return Reflect.setPrototypeOf(...arguments)
}
});
Object.setPrototypeOf(proxy, Object);
// setPrototypeOf()
10.isExtensible捕获器
isExtensible()捕获器会在Object.isExtensible()中被调用。对应的反射API方法为Reflect.isExtensible()。
(1)返回值
isExtensible()必须返回布尔值,表示target是否可扩展。返回非布尔值会被转型为布尔值。
(2)处理程序参数
- target:目标对象。
(3)捕获器不变式
- 如果target可扩展,则处理程序必须返回true。
- 如果target不可扩展,则处理程序必须返回false。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
isExtensible(target) {
console.log('isExtensible()');
return Reflect.isExtensible(...arguments)
}
});
Object.isExtensible(proxy);
// isExtensible()
11.preventExtensions捕获器
preventExtensions()捕获器会在Object.preventExtensions()中被调用。对应的反射API方法为Reflect.preventExtensions()。
(1)返回值
preventExtensions()必须返回布尔值,表示target是否已经不可扩展。返回非布尔值会被转型为布尔值。
(2)处理程序参数
- target:目标对象。
(3)捕获器不变式
- 如果Object.isExtensible(proxy)是false,则处理程序必须返回true。
(4)示例
const myTarget = {};
const proxy = new Proxy(myTarget, {
preventExtensions(target) {
console.log('preventExtensions()');
return Reflect.preventExtensions(...arguments)
}
});
Object.preventExtensions(proxy);
// preventExtensions()
12.apply捕获器
apply()捕获器会在调用函数时中被调用。对应的反射API方法为Reflect.apply()。
(1)返回值
返回值无限制。
(2)处理程序参数
- target:目标对象。
- thisArg:调用函数时的this参数。
- argumentsList:调用函数时的参数列表
(3)捕获器不变式
- target必须是一个函数对象。
(4)示例
const myTarget = () => {};
const proxy = new Proxy(myTarget, {
apply(target, thisArg, ...argumentsList) {
console.log('apply()');
return Reflect.apply(...arguments)
}
});
proxy();
// apply()
13.construct捕获器
construct()捕获器会在new操作符中被调用。对应的反射API方法为Reflect.construct()。
(1)返回值
construct()必须返回一个对象。
(2)处理程序参数
- target:目标构造函数。
- argumentsList:传给目标构造函数的参数列表。
- newTarget:最初被调用的构造函数。
(3)捕获器不变式
- target必须可以用作构造函数。
(4)示例
const myTarget = function() {};
const proxy = new Proxy(myTarget, {
construct(target, argumentsList, newTarget) {
console.log('construct()');
return Reflect.construct(...arguments)
}
});
new proxy;
// construct()
五.代理中的问题与不足
代理虽然为开发者提供了对象基本操作的拦截和行为改变的便捷功能,极大丰富了编程的灵活性,但在某些情况下,代理也不能与现在的ECMAScript机制很好地协同。
1.代理中的this
代理中的this指代的是代理对象本身,如果目标对象用了this关键字做为键值或者一些判断操作,在使用代理对象时就会出现问题。因为在通过代理对象透传到目标对象上的this指代的是代理对象,而不是目标对象。
2.代理与内部插槽
代理与极大多数内置引用类型都可以很好的协同。但有些ECMAScript内置类型在使用代理时可能会报错。比如Date类型,根据ECMAScript规范,Date类型方法的执行依赖this值上的内部槽位[[NumberDate]]。在使用代理对象时,this指代的是代理对象,不存在这个内部槽位,于是代理拦截后本应转发给目标对象的方法会抛出错误。