代理基础
new Proxy(target,handler)
- target:要使用Proxy包装的对象,可以是任何类型的对象,包括原生数组,函数以及另一个代理
- handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理的行为 缺少了其中一个都会抛出TypeError
在代理对象上执行的任何操作实际上都会应用到目标对象,唯一可感知不同的就是操作的是代理对象
<script>
const obj = {
age:17
}
const pxy = new Proxy(obj,{});
console.log(pxy.age,obj.age);//17 17
</script>
给目标属性赋值会反映在两个对象上,因为两个对象访问的是同一个值, 给代理属性赋值会反映在两个对象上,因为这个赋值会转移到目标对象上
Proxy.prototype是undefined,所以不能使用instanceof操作符 可以使用严格等于来区分代理和目标
const obj = {
age:17
}
const pxy = new Proxy(obj,{})
console.log(pxy === obj);//false
定义捕获器
使用代理的主要目的是可以定义捕获器,捕获器就是在处理程序对象中定义的基本操作的拦截器,每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上进行调用,每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为
注意,只有在代理对象上执行某些操作才会触发捕获器,在目标对象上执行这些操作仍然会产生正常的行为
<script>
const obj = {
age:17
}
const pxy = new Proxy(obj,{
get(){
return 18
}
});
console.log(obj.age,pxy.age);//17,18
</script>
捕获器参数和反射API
所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为,比如:get()捕获器会接收到目标对象,要查询的属性和代理对象三个参数,有了这些参数,就可以重建被捕获方法的原始行为
<script>
const obj = {
age:17
}
const pxy = new Proxy(obj,{
get(mub,shux,pro){
return mub[shux] = 19
}
});
console.log(obj.age,pxy.age);//17,19
</script>
但是通过手动重构原始行为的想法是不显示的,所以js提供了反射API(Reflect对象),可以通过调用全局Reflect对象上的同名方法进行重建
处理对象中所有可以进行捕获的方法都有相应的反射API的方法,这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为
可撤销代理
有时候可能需要中断代理对象与目标对象之间的联系。对于使用new Proxy()创建的普通代理来说,这种联系会在代理对象的生命周期内一直持续存在
Proxy也暴露了revocable()方法,这个方法支持撤销代理对象与目标对象的关联。撤销代理的操作是不可逆的,而且不管调用多少次,结果都是一样的,撤销之后再调用代理会抛出TypeError
撤销函数和代理对象是在实例化时同时生成的
<script>
const obj = {
age:17
}
const {proxy:pro,revoke} = Proxy.revocable(obj,{});
console.log(pro.age);//17
revoke()
console.log(pro.age);//TypeError
</script>
实用反射API
某些情况下应该优先使用反射API 在使用反射API时要记住:
- 反射API并不限于捕获处理程序
- 大多数反射API方法在Object类型上有对应的方法
通常,Object上的方法适用于通用程序,而反射方法用于细粒级的对象控制和操作
很多反射方法返回称作"状态标记"的布尔值,表示意图执行的操作是否成功,以下反射方法都会提供状态标记:
-
Reflect.defineProperty()
-
Reflect.preventExtensions()
-
Reflect.setPrototypeOf()
-
Reflect.set()
-
Reflect.deleteProperty() 以下反射方法提供只有通过操作符才能完成的操作:
-
Reflect.get():可以替代对象属性访问操作符
-
Reflect.set():可以替代=赋值操作符
-
Reflect.has():可以替代in操作符或with()
-
Reflect.deleteProperty():可以替代delete操作符
-
Reflect.construct():可以替代new操作符
代理另一个代理
代理可以拦截反射API的操作,而这意味着完全可以创建一个代理,通过它去代理另一个代理。这样就可以在一个目标对象之上构建多层拦截网
<script>
const obj = {
age:17
}
const first = new Proxy(obj,{
get(x,y){
return x.y = 16
}
})
const second = new Proxy(first,{
get(x,y){
return x.y = 15
}
})
console.log(second.age);
</script>
代理捕获器与反射方法
代理可以捕获13种不同的基本操作。这些操作有各自不同的反射API方法、参数、关联ECMAScript操作和不变式 在代理对象上执行任何一种操作,只会有一种捕获处理程序被调用,不会存在重复捕获的情况
get()
get()捕获器在获取属性值的操作中被调用,对应的反射API方法为Reflect.get()
- 返回值:没有限制
- 拦截的操作:
- proxy.property
- proxy[property]
- Object.create(proxy)[property]
- Reflect.get(proxy, property, receiver)
- 捕获器处理程序参数:
- 目标对象(target)
- 引用的目标对象上的字符串键属性(property)
- 代理对象或继承代理对象的对象(receiver)
- 捕获器不变式
- 如果target.property不可写且不可配置,则处理程序返回的值必须与target.property匹配
- 如果target.property不可配置且[[Get]]特性为undefined,处理程序的返回值也必须是undefined
set()
会在设置属性值的操作中被调用,对应的反射API为Reflect.set()
- 返回值
- true表示设置成功
- false表示设置失败
- 拦截的操作
- proxy.property = value
- proxy[property] = value
- Object.create(proxy)[property] = value
- Reflect.set(proxy, property, value, receiver)
- 捕获器处理程序参数
- 目标对象
- 引用的目标对象的字符串键属性
- 要赋值给属性的值
- 接收最初赋值对象
- 捕获器不变式
- 如果target.property不可写且不可配置,则不能修改目标属性的值
- 如果target.property不可配置且[[Set]]特性为undefined,则不能修改目标属性的值
- 在严格模式下,处理程序中返回false会抛出TypeError
has()
会在in操作符中被调用,对应的反射API的方法是Reflect.has()
- 返回值:
- 必须是布尔值,表示属性是否存在,返回非布尔值会被转换为布尔值
- 拦截的操作
- property in proxy
- property in Object.create(proxy)
- with(proxy) {(property); }
- Reflect.has(proxy, property)
- 捕获器处理程序参数
- 目标对象
- 引用的目标对象上的字符串键属性
- 捕获器不变式
- 如果target.property存在且不可配置,则处理程序必须返回true
- 如果target.property存在且目标对象不可扩展,则处理程序必须返回true
defineProperty()
会在Object.defineProperty()中被调用。对应的反射API方法为Reflect.defineProperty()
- 返回值
- defineProperty()必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值
- 拦截的操作
- Object.defineProperty(proxy, property, descriptor)
- Reflect.defineProperty(proxy, property, descriptor)
- 捕获器处理程序参数
- 目标对象
- 引用的目标对象的字符串键属性
- 包含可选的enumerable、configurable、writable、value、get和set定义的对象
- 捕获器不变式
- 如果目标对象不可扩展,则无法定义属性
- 如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性
- 如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性
getOwnPropertyDescriptor()
会在Object.getOwnPropertyDescriptor()中被调用。对应的反射API方法为Reflect.getOwnPropertyDescriptor()
- 返回值
- getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回undefined
- 拦截的操作
- Object.getOwnPropertyDescriptor(proxy, property)
- Reflect.getOwnPropertyDescriptor(proxy, property)
- 捕获器处理程序参数
- 目标对象
- 引用的目标对象上的字符串键属性
- 捕获器不变式
- 如果自有的target.property存在且不可配置,则处理程序必须返回一个表示该属性存在的对象
- 如果自有的target.property存在且可配置,则处理程序必须返回表示该属性可配置的对象
- 如果自有的target.property存在且target不可扩展,则处理程序必须返回一个表示该属性存在的对象
- 如果target.property不存在且target不可扩展,则处理程序必须返回undefined表示该属性不存在
- 如果target.property不存在,则处理程序不能返回表示该属性可配置的对象
deleteProperty()
会在delete操作符中被调用。对应的反射API方法为Reflect. deleteProperty()
- 返回值
- deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值
- 拦截的操作
- delete proxy.property
- delete proxy[property]
- Reflect.deleteProperty(proxy, property)
- 捕获器处理程序参数
- 目标对象
- 引用目标对象上的字符串键属性
- 捕获器不变式
- 如果自有的target.property存在且不可配置,则处理程序不能删除这个属性
ownKeys()
会在Object.keys()及类似方法中被调用。对应的反射API方法为Reflect. ownKeys()
- 返回值
- ownKeys()必须返回包含字符串或符号的可枚举对象
- 拦截的操作
- Object.getOwnPropertyNames(proxy)
- Object.getOwnPropertySymbols(proxy)
- Object.keys(proxy)
- Reflect.ownKeys(proxy)
- 捕获器处理程序参数
- target:目标对象
- 捕获器不变式
- 返回的可枚举对象必须包含target的所有不可配置的自有属性
- 如果target不可扩展,则返回可枚举对象必须准确地包含自有属性键
getPrototypeOf()
会在Object.getPrototypeOf()中被调用。对应的反射API方法为Reflect.getPrototypeOf()
- 返回值
- getPrototypeOf()必须返回对象或null
- 拦截的操作
- Object.getPrototypeOf(proxy)
- Reflect.getPrototypeOf(proxy)
- proxy.proto
- Object.prototype.isPrototypeOf(proxy)
- proxy instanceof Object
- 捕获器处理程序参数
- 目标对象
- 捕获器不变式
- 如果target不可扩展,则Object.getPrototypeOf(proxy)唯一有效的返回值就是Object. getPrototypeOf(target)的返回值
setPrototypeOf()
会在Object.setPrototypeOf()中被调用。对应的反射API方法为Reflect.setPrototypeOf()
- 返回值
- setPrototypeOf()必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值
- 拦截的操作
- Object.setPrototypeOf(proxy)
- Reflect.setPrototypeOf(proxy)
- 捕获器处理程序参数
- 目标对象
- prototype:target的替代原型,如果是顶级原型则为null
- 捕获器不变式
- 如果target不可扩展,则唯一有效的prototype参数就是Object.getPrototypeOf(target)的返回值
isExtensible()
会在Object.isExtensible()中被调用。对应的反射API方法为Reflect.isExtensible()
- 返回值
- isExtensible()必须返回布尔值,表示target是否可扩展。返回非布尔值会被转型为布尔值。
- 拦截的操作
- Object.isExtensible(proxy)
- Reflect.isExtensible(proxy)
- 捕获器处理程序参数
- 目标对象
- 捕获器不变式
- 如果target可扩展,则处理程序必须返回true
- 如果target不可扩展,则处理程序必须返回false
preventExtensions()
会在Object.preventExtensions()中被调用。对应的反射API方法为Reflect.preventExtensions()
- 返回值
- preventExtensions()必须返回布尔值,表示target是否已经不可扩展。返回非布尔值会被转型为布尔值
- 拦截的操作
- Object.preventExtensions(proxy)
- Object.preventExtensions(proxy)
- 捕获器处理程序参数
- 目标对象
- 捕获器不变式
- 如果Object.isExtensible(proxy)是false,则处理程序必须返回true。
apply()
会在调用函数时中被调用。对应的反射API方法为Reflect.apply()
- 返回值
- 无限制
- 拦截的操作
- proxy(...argumentsList)
- Function.prototype.apply(thisArg, argumentsList)
- Function.prototype.call(thisArg, ...argumentsList)
- Reflect.apply(target, thisArgument, argumentsList)
- 捕获器处理程序参数
- 目标对象
- 调用函数时this的参数
- 调用函数时的参数列表
- 捕获器不变式
-
必须是一个函数对象
-
construct()
会在new操作符中被调用。对应的反射API方法为Reflect.construct()
- 返回值
- 必须返回一个对象
- 拦截的操作
- new proxy(...argumentsList)
- Reflect.construct(target, argumentsList, newTarget)
- 捕获器处理程序参数
- 目标构造函数
- 传给目标构造函数的参数列表
- 最初被调用的构造函数
- 捕获器不变式
- target必须可以用作构造函数