《JavaScript 高级程序设计》第7章 代理与反射
7.代理与反射
代理基础
代理使用 Proxy 构造函数创建,接收两个参数:目标对象和处理程序对象。传一个简单的对象字面量即可创建一个空代理。
const target = {
id: 'target'
}
const proxy = new Proxy(target, {})
console.log(proxy.id) // target
proxy.id = 'foo'
console.log(proxy.id) // foo
console.log(target.id) // foo
使用代理的主要目的是可以定义捕获器。捕获器就是在处理程序对象中定义的“基本操作的拦截器”。例如定义一个 get() 捕获器,当通过代理对象执行 get 操作时就会触发定义好的捕获器。
const target = {
foo: 'bar'
}
const proxy = new Proxy(target, {
get() {
return 'handler override
}
})
console.log(proxy.foo) // handler override
所有捕获器都可以访问相应的参数,重建被捕获方法的原始行为。例如 get 捕获器可以接收:目标对象、要查询的属性和代理对象三个参数。处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API方法。例如 get 捕获器:
const target = {
foo: 'bar'
}
const handler = {
get: Reflect.get
}
const proxy = new Proxy(target, handler)
反射API为开发者准备好了样板代码,在此基础上可以用最少的代码修改捕获方法,例如在属性被访问时对返回的值进行修饰:
const target = {
foo: 'bar',
bar: 'qux'
}
const handler = {
get(trapTarget, property, receiver) {
let decoration = ''
if (property === 'foo') {
decoration = '!!!'
}
return Reflect.get(...arguments) + decoration
}
}
const proxy = new Proxy(target, handler)
console.log(proxy.foo) // bar!!!
console.log(proxy.bar) // qux
捕获处理程序的行为必须遵循捕获器不变式,用于防止捕获器定义出现过去反常的行为。 Proxy 暴露了 revocable 方法,支持撤销代理对象与目标对象的关联。撤销代理的操作是不可逆的。
const target = {
foo: 'bar'
}
const handler = {
get() {
return 'intercepted'
}
}
const {proxy, revoke} = Proxy.revocable(target, handler)
console.log(proxy.foo) // intercepted
revoke()
console.log(proxy.foo) // TypeError
代理的问题和不足:
- 代理中的 this 问题
- 代理与内部槽位的问题(例如 Date)
代理捕获器与反射方法
get()
- 返回值:无限制
- 拦截的操作:
- proxy.property
- proxy[property]
- Object.create(proxy)[property]
- Reflect.get(proxy, property, receiver)
- 处理程序参数:
- target 目标对象
- property 属性名
- receiver 代理对象
- 不变式
- 如果属性不可写且不可配置,处理程序返回的值必须和属性的值一致
- 如果属性不可配置且 [[Get]] 特性为 undefined ,处理程序的返回值也必须是 undefined
set()
- 返回值:true 为成功,false 表示失败
- 拦截的操作:
- proxy.property = value
- proxy[property] = value
- Object.create(proxy)[property] = value
- Reflect.set(proxy, property, value, receiver)
- 处理程序参数:
- target 目标对象
- property 属性名
- value 赋给属性的值
- receiver 代理对象
- 不变式
- 如果属性不可写且不可配置,则不能修改目标属性的值
- 如果属性不可配置且 [[Set]] 特性为 undefined ,则不能修改属性的值
- 严格模式下处理程序返回 false 会抛出 TypeError
has()
- 返回值:必须返回布尔值,表示属性是否存在。非布尔值会被转型为布尔值
- 拦截的操作:
- property in proxy
- property in Object.create(proxy)
- with(proxy) {(property)}
- Reflect.has(proxy, property)
- 处理程序参数:
- target 目标对象
- property 属性名
- 不变式
- 如果属性存在且不可配置,处理程序必须返回 true
- 如果属性存在且目标对象不可扩展,处理程序必须返回 true
defineProperty()
- 返回值:必须返回布尔值,表示属性是否成功定义。非布尔值会被转型为布尔值
- 拦截的操作:
- Object.defineProperty(proxy, property, descriptor)
- Reflect.defineProperty(proxy, property, descriptor)
- 处理程序参数:
- target 目标对象
- property 属性名
- descriptor 包含可选的 enumerable、configurable、writable、value、get 和 set 定义的对象
- 不变式
- 如果目标对象不可扩展,则无法定义属性
- 如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性
- 如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性
getOwnPropertyDescriptor()
- 返回值:必须返回对象,或在属性不存在时返回 undefined
- 拦截的操作:
- Object.getOwnPropertyDescriptor(proxy, property)
- Reflect.getOwnPropertyDescriptor(proxy, property)
- 处理程序参数:
- target 目标对象
- property 属性名
- 不变式
- 如果自有的属性存在且不可配置,则处理程序必须返回一个表示该属性存在的对象
- 如果自有的属性存在且可配置,则处理程序必须返回表示该属性可配置的对象
- 如果自有属性存在且目标对象不可扩展,则处理程序必须返回一个表示该属性存在的对象
- 如果属性不存在且目标对象不可扩展,则处理程序返回 undefined
- 如果属性不存在,则处理程序不能返回表示该属性可配置的对象
deleteProperty()
- 返回值:必须返回布尔值,表示属性是否成功删除。非布尔值会被转型为布尔值
- 拦截的操作:
- delete proxy.property
- delete proxy[property]
- Reflect.deleteProperty(proxy, property)
- 处理程序参数:
- target 目标对象
- property 属性名
- 不变式
- 如果自有的属性存在且不可配置,则处理程序不能删除这个属性
ownKeys()
- 返回值:必须返回包含字符串或符号的可枚举对象
- 拦截的操作:
- Object.getOwnPropertyNames(proxy)
- Object.getOwnPropertySymbols(proxy)
- Object.keys(proxy)
- Reflect.ownKeys(proxy)
- 处理程序参数:
- target 目标对象
- 不变式
- 返回的可枚举对象必须包含 target 的所有不可配置的自由属性
- 如果 target 不可扩展,则返回可枚举对象必须准确的包含自有属性键
getPrototypeOf()
- 返回值:必须返回对象或 null
- 拦截的操作:
- Object.getPrototypeOf(proxy)
- Reflect.getPrototypeOf(proxy)
- proxy.proto
- Object.prototype.isPrototypeOf(proxy)
- proxy instanceof Object
- 处理程序参数:
- target 目标对象
- 不变式
- 如果 target 不可扩展,则 Object.getPrototypeOf(proxy) 唯一有效返回值就是 Object.getPrototypeOf(target) 的返回值
setPrototypeOf()
- 返回值:必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转换为布尔值
- 拦截的操作:
- Object.setPrototypeOf(proxy)
- Reflect.setPrototypeOf(proxy)
- 处理程序参数:
- target 目标对象
- prototype target 的替代原型,如果是顶级原型则为 null
- 不变式
- 如果 target 不可扩展,则唯一有效的 prototype 参数就是 Object.getPrototypeOf(target) 的返回值
isExtensible()
- 返回值:必须返回布尔值,表示 target 是否可拓展。返回非布尔值会被转换为布尔值
- 拦截的操作:
- Object.isExtensible(proxy)
- Reflect.isExtensible(proxy)
- 处理程序参数:
- target 目标对象
- 不变式
- 如果 target 可扩展,则处理程序必须返回 true
- 如果 target 不可扩展,则处理程序必须返回 false
preventExtensions()
- 返回值:必须返回布尔值,表示 target 是否不可拓展。返回非布尔值会被转换为布尔值
- 拦截的操作:
- Object.preventExtensions(proxy)
- Reflect.preventExtensions(proxy)
- 处理程序参数:
- target 目标对象
- 不变式
- 如果 Object.isExtensible(proxy) 是 false ,则处理程序必须返回 true
apply()
- 返回值:无限制
- 拦截的操作:
- proxy(...argumentsList)
- Function.prototype.apply(thisArg, argumentsList)
- Function.prototype.call(thisArg, ...argumentsList)
- Relect.apply(target, thisArgument, argumentsList)
- 处理程序参数:
- target 目标对象
- thisArg 调用函数时的 this 参数
- argumentsList 调用函数时的参数列表
- 不变式
- target 必须是一个函数对象
construct()
- 返回值:必须返回一个对象
- 拦截的操作:
- new Proxy(...argumentsList)
- Reflect.construct(target, argumentsList, newTarget)
- 处理程序参数:
- target 目标构造函数
- argumentsList 传给目标构造函数的参数列表
- newTarget 最初被调用的构造函数
- 不变式
- target 必须可以用作构造函数
代理模式
使用代理可以在代码中实现一些有用的编程模式:
- 跟踪属性访问
- 隐藏属性
- 属性验证
- 函数与构造函数参数验证
- 数据绑定与可观察对象
相关文章