Skip to content
On this page

Proxy 和 Reflect

Proxy 和 Reflect 为开发者提供了拦截并向基本操作嵌入额外行为的能力。即可以为目标对象定义一个关联的代理对象,而这个代理对象是可以作为抽象的目标对象来使用。在对目标对象的操作影响目标对象之前,可以在代理对象中对这些操作加以控制。

创建 proxy

创建 proxy 可以通过 new Proxy 构造函数,构造函数可以接收两个参数,即 目标对象 和 处理程序对象(handler),下面是一个空代理的例子,

js
let target = {
  id: "target",
}

let handler = {}
// target 目标对象
// handler 处理程序对象
const proxy = new Proxy(target, handler)

此时,因为只是一个空代理,所以 target 和 proxy 实例的属性值始终会保持一致,

js
target.id === proxy.id // true

// 给目标对象添加属性时,proxy 也能访问到这个值
target.type = "all"
console.log(proxy.type) // all

// 同样,proxy 上的操作也会转移到目标对象上来
proxy.skill = "English"
console.log(target.skill) // English

target === proxy // false

处理程序对象

创建 proxy 的时候可以在处理对象中传入捕获器,每个处理对象可以包含零个或多个捕获器,每次对代理对象调用基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。

proxy 拥有的处理器列表如下

handler 方法何时触发
get读取属性
set设置属性值
hasin
deletePropertydelete operator
applyfunction call
constructnew operator
getPrototypeOfObject.getPrototypeOf
setPrototypeOfObject.setPrototypeOf
isExtensibleObject.isExtensible
preventExtensionsObject.preventExtensions
definePropertyObject.defineProperty, Object.defineProperties
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor, for..in, Object.keys/values/entries
ownKeysObject.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in, Object.keys/values/entries

我们先来看看常用的几个 handler 捕获器,就从get,set开始吧

js
let obj = {
  name: "lo",
}

let proxy = new Proxy(obj, {
  get(target, property) {
    return target[property]
  },
  set(target, property, value) {
    target[property] = value
    return true
  },
})

proxy[name] //执行 get
proxy.gender = "male" // 执行 set

在这之后,对 proxy 的操作,都要先执行对应的捕获器,当目标对象被代理后,就不再应该操作目标对象了,所以可以直接用代理对象来替代目标对象

js
let obj = {}

obj = new Proxy(obj, {})

Reflect

Reflect 是一个内建对象,可简化 Proxy 的创建

每个捕获器方法,在 Reflect 内部都有对应的方法,且名称与参数都有捕获器相同,上面的例子可以用 Reflect 改写为

js
let obj = {
  name: "lo",
}

let proxy = new Proxy(obj, {
  get(target, property) {
    // return target[property]
    return Reflect.get(target, property)
  },
  set(target, property, value) {
    // target[property] = value
    Reflect.set(target, property, value)
    return true
  },
})

Proxy 的局限

许多内建对象,都使用了内建插槽,比如 Map、Date 等 // TODO:

可撤销 Proxy

Proxy.revocable方法可以返回 Proxy 对象和撤销 Proxy 的方法 revoke

js
let user = {
  name: "tom",
}
let { proxy, revoke } = Proxy.revocable(user, {})

revoke()

proxy.name // Error