首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

深入了解 Proxy 代理

代理对象封装另一个对象并拦截操作,如读取/写入属性和其他操作,可以选择自己处理它们,或透明地允许对象处理它们。

很多库和一些浏览器框架都使用代理。在本文中,我们将看到许多实际应用程序。

Proxy

语法如下:

target - 是一个要包装的对象,可以是任何东西,包括函数。

handler - 代理配置:一个带有“陷阱”的对象,拦截操作的方法。-例如,读取target属性时设置trap,写入target属性时设置trap,等等。

对于代理上的操作,如果handler中有相应的陷阱,那么它就会运行,并且代理有机会处理它,否则操作就会在目标上执行。

作为一个开始的例子,让我们创建一个没有任何陷阱的代理:

由于没有陷阱,代理上的所有操作都被转发到目标。

写操作 proxy.test=target上的值。

读取操作 proxy.test 从 target 返回值。

迭代代理返回目标值。

正如我们所见,没有任何陷阱,proxy是一个透明的目标包装器。

Proxy是一种特殊的“外来对象”。它没有自己的属性。使用空处理程序,它透明地将操作转发给target。

为了激活更多的功能,让我们添加陷阱。

我们能用他们拦截什么?

对于对象上的大多数操作,JavaScript规范中都有一个所谓的“内部方法”,它描述了它在最低级别的工作方式。例如[[Get]],读取属性的内部方法,[[Set]],写入属性的内部方法,等等。这些方法仅在规范中使用,我们不能直接通过名称调用它们。

代理陷阱拦截这些方法的调用。它们在代理规范和下表中列出。

对于每个内部方法,在该表中都有一个陷阱:我们可以添加到新代理的handler参数的方法名来拦截操作:

使用 get 方式获取默认值

最常见的陷阱是用于读/写属性的。

为了拦截读取,处理程序应该有一个方法get(目标、属性、接收器)。

当一个属性被读取时,它会触发,参数如下:

target—是目标对象,作为第一个参数传递给新代理,

property -属性名称,

receiver——如果目标属性是一个getter,那么receiver就是将在其调用中使用的对象。通常这是代理对象本身(或者从它继承的对象,如果我们从代理继承的话)。现在我们不需要这个论证,所以后面会更详细地解释。

让我们使用get来实现对象的默认值。

我们将创建一个数字数组,对于不存在的值返回0。

通常,当一个人试图获取一个不存在的数组项时,他们得到的是未定义的,但是我们将把一个常规的数组包装到代理中,以捕获读取,如果没有这样的属性则返回0:

正如我们所见,诱捕陷阱很容易做到。

我们可以使用代理来实现“默认”值的任何逻辑。

想象一下我们有一本词典,里面有一些短语和它们的翻译:

现在,如果没有短语,从字典中读取将返回undefined。但在实践中,不翻译一个短语通常比不定义要好。我们让它返回一个未翻译的短语,而不是undefined。

为了实现这一点,我们将把dictionary封装在一个拦截读取操作的代理中:

使用 set 验证

假设我们想要一个专门用于数字的数组。如果添加了另一种类型的值,应该会出现错误。

set trap在写入属性时触发。

target—是目标对象,作为第一个参数传递给新代理,

property -属性名称,

value -属性值,

receiver——与get trap类似,只对setter属性有效。

如果设置成功,set trap应该返回true,否则返回false(触发TypeError)。

让我们使用它来验证新值:

请注意:数组的内置功能仍然有效!值是通过push添加的。当添加值时,length属性自动增加。我们的代理不会破坏任何东西。

我们不必重写添加值的数组方法(如push和unshift等)来添加检查,因为它们在内部使用由代理拦截的[[Set]]操作。

因此,代码是干净和简洁的。

使用 ownKeys, getOwnPropertyDescriptor 进行迭代

Object.keys, for...in 和迭代对象属性的大多数其他方法使用[[OwnPropertyKeys]]内部方法(被ownKeys陷阱截获)来获得属性列表。

这些方法在细节上有所不同:

Object.getOwnPropertyNames(obj) 返回非符号键。

Object.getOwnPropertySymbols(obj) 返回符号键。

Object.keys/values()返回带有可枚举标志的非符号键/值(属性标志在“属性标志和描述符”一文中解释过)。

for..in 循环遍历带有enumerable标志的非符号键和原型键。

不过,如果返回对象中不存在的键,则返回Object.keys不会列出它:

为什么?原因很简单:只返回带有标志的属性。为了检查它,它调用每个属性的内部方法来获取它的描述符。这里,因为没有属性,它的描述符是空的,没有可枚举标志,所以它被跳过。

为对象。要返回一个属性,我们需要它存在于对象中,并带有标志,或者可以拦截对的调用(陷阱做了这个工作),并返回一个带有的描述符。

使用 deleteProperty 保护属性

有一个广泛的约定,即以下划线为前缀的属性和方法是内部的。它们不应该从对象外部访问。

从技术上讲,这是可能的:

让我们使用代理来防止任何以_开头的属性的访问。

我们需要陷阱:

读取这样的属性时抛出错误,

设置为写入时抛出错误,

删除时抛出错误,

排除以_开头的属性和方法,如。

请注意get陷阱的重要细节,在(*)行:

为什么我们需要一个函数来调用`value.bind(target)``?

原因是对象方法,如,必须能够访问:

使用 has in range

我们想使用in操作符来检查一个数字是否在范围内。

has陷阱在调用中拦截。

has(target, property)

target — 是目标对象,作为第一个参数传递给新代理,

property -属性名称

演示:

包装函数:apply

我们也可以用代理来封装函数。

陷阱将调用代理作为函数:

是目标对象(是中的对象),

是的值。

是一个参数列表。

正如我们已经看到的,这基本上是可行的。包装器函数(*)在超时后执行调用。

但是包装器函数不转发属性读/写操作或其他任何操作。包装后,对原始函数的属性的访问将丢失,例如名称、长度等:

理要强大得多,因为它将所有内容转发给目标对象。

让我们使用代理代替包装函数:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20210316A039YV00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券