《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

代理的问题和不足:

  1. 代理中的 this 问题
  2. 代理与内部槽位的问题(例如 Date)

代理捕获器与反射方法

get()

set()

has()

defineProperty()

getOwnPropertyDescriptor()

deleteProperty()

ownKeys()

getPrototypeOf()

setPrototypeOf()

isExtensible()

preventExtensions()

apply()

construct()

代理模式

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

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