《JavaScript 高级程序设计》第7章 代理与反射

2024年03月12日

7.代理与反射

代理基础

代理使用 Proxy 构造函数创建,接收两个参数:目标对象和处理程序对象。传一个简单的对象字面量即可创建一个空代理。

javascript 复制代码
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 操作时就会触发定义好的捕获器。

javascript 复制代码
const target = {
  foo: 'bar'
}
const proxy = new Proxy(target, {
  get() {
    return 'handler override
  }
})
console.log(proxy.foo) // handler override

所有捕获器都可以访问相应的参数,重建被捕获方法的原始行为。例如 get 捕获器可以接收:目标对象、要查询的属性和代理对象三个参数。处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API方法。例如 get 捕获器:

javascript 复制代码
const target = {
  foo: 'bar'
}
const handler = {
  get: Reflect.get
}
const proxy = new Proxy(target, handler)

反射API为开发者准备好了样板代码,在此基础上可以用最少的代码修改捕获方法,例如在属性被访问时对返回的值进行修饰:

javascript 复制代码
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 方法,支持撤销代理对象与目标对象的关联。撤销代理的操作是不可逆的。

javascript 复制代码
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

代理的问题和不足:

  1. 代理中的 this 问题
  2. 代理与内部槽位的问题(例如 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 必须可以用作构造函数

代理模式

使用代理可以在代码中实现一些有用的编程模式:

  1. 跟踪属性访问
  2. 隐藏属性
  3. 属性验证
  4. 函数与构造函数参数验证
  5. 数据绑定与可观察对象
相关文章

Vite项目配置本地HTTPS

React Native 开发环境安装踩坑

《JavaScript 高级程序设计》第10-16章