定义

Proxy这个单词,翻译过来,就是代理,是 ES6 中提供的新 API Proxy对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等) (在文档中被称为 traps(陷阱),我觉得可以理解为针对对象各种行为的钩子)

语法

const p = new Proxy(target, handler)

参数

target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

基础例子

let target = {}
let handlers = {} // do nothing
let proxy = new Proxy(target, handlers)

proxy.a = 123

console.log(target.a) // 123

在第二个参数为空对象的情况下,基本可以理解为是对第一个参数做的一次浅拷贝(Proxy必须是浅拷贝,如果是深拷贝则会失去了代理的意义)

对比对象的 gettersetter属性

let obj = {
  _age: 20,
  get age() {
    return `I'm ${this._age} years old`
  },
  set age(val) {
    this._age = Number(val)
  }
}

console.log(obj.age) // I'm 20 years old
obj.age = 21
console.log(obj.age) // I'm 21 years old

就像这段代码描述的一样,我们设置了一个属性 _age,然后又设置了一个 get ageset age 然后我们可以直接调用 obj.age 来获取一个返回值,也可以对其进行赋值。 这么做有几个缺点

  1. 针对每一个要代理的属性都要编写对应的 gettersetter
  2. 必须还要存在一个存储真实值的key(如果我们直接在getter里边调用this.age则会出现堆栈溢出的情况,因为无论何时调用this.age进行取值都会触发getter)。 Proxy很好的解决了这两个问题:
let obj = {
  name: ‘hk’,
  age: 20
}
let handlers = {
	get(target, property) {
    if (property === ‘name') {
      return `My name is ${target[property]}`
    } else if (property === ‘age’) {
      return `I am ${target[property]} years old`
    }
    return target[property]
  }
  set (target, property, value) {
    target[property] = value
  }
}
let proxy = new Proxy(obj, handlers)

console.log(proxy.name, obj.name) // My name is hk        hk
console.log(proxy.age, obj.age) // I'm 20 years old       20
obj.age = 21
console.log(obj.age) // 21

我们通过创建getset两个trap来统一管理所有的操作,可以看到,在修改proxy的同时,obj的内容也被修改,而且我们对proxy的行为进行了一些特殊的处理。

而且我们无需额外的用一个key来存储真实的值,因为我们在trap内部操作的是target对象,而不是proxy对象

确定一个对象是否是一个代理是不可能的 根据Javascript语言规范,无法确定对象是否是代理。 但是,在 Node 10+上,可以使用 util.types.isProxy 方法。

参考

Proxy