在使用对象时,我们可以创建一个代理对象来拦截和改变现有对象的行为。

我们使用在ES2015引入的Proxy原生对象来实现。

假设我们有一个car对象:

const car = {
 color: 'blue'
}

一个非常简单的例子是当我们尝试访问一个不存在的属性时返回一个‘Not found’字符串。

你可以定义一个代理对象,每当你尝试访问这个对象的属性时都会被调用。

你可以通过创建另一个对象来实现,这个对象具有一个get()方法,接收目标对象和属性作为参数:

const car = {
 color: 'blue'
}

const handler = {
 get(target, property) {
 return target[property] ?? 'Not found'
 }
}

然后我们可以通过调用new Proxy()来初始化我们的代理对象,传递原始对象和我们的处理程序:

const proxyObject = new Proxy(car, handler)

现在尝试访问car对象中包含的属性,但是从proxyObject引用它:

proxyObject.color //'blue'

这就像调用car.color一样。

但是当你尝试访问car上不存在的属性,比如car.test,你将得到undefined。使用代理,你将得到我们告诉它返回的'Not found'字符串。

proxyObject.test //'Not found'

我们在代理处理程序中不仅限于使用get()方法。这只是我们可以编写的最简单的例子。

我们还有其他可以使用的方法:

  • apply当我们在对象上使用apply()时调用这个方法
  • construct当我们访问对象的构造函数时调用已经被执行
  • deleteProperty当我们尝试删除一个属性时被执行
  • defineProperty当我们在对象上定义一个新的属性时调用
  • set当我们尝试设置一个属性时被执行

等等。基本上我们可以创建一个受保护的门,来控制一个对象上发生的一切,并提供其他规则和控制来实现我们自己的逻辑。

我们还可以使用其他方法(也称为陷阱):

  • enumerate
  • getOwnPropertyDescriptor
  • getPrototypeOf
  • has
  • isExtensible
  • ownKeys
  • preventExtensions
  • setPrototypeOf

所有这些都对应于相应的功能。

你可以在MDN上阅读更多关于这些的说明

让我们再举一个使用deleteProperty的例子。我们想要防止删除对象的属性:

const car = {
 color: 'blue'
}

const handler = {
 deleteProperty(target, property) {
 return false
 }
}

const proxyObject = new Proxy(car, handler)

如果我们调用delete proxyObject.color,我们将得到一个TypeError:

TypeError: 'deleteProperty' on proxy: trap returned falsish for property 'color'

当然,你总是可以直接在car对象上删除属性,但如果你编写的逻辑使该对象不可访问,只暴露代理对象,那就是封装你的逻辑的一种方式。