409- ES6之Proxy 的巧用

点击上方“前端自习课”关注,学习起来~

Proxy 介绍

使用 Proxy ,你可以将一只猫伪装成一只老虎。下面大约有6个例子,我希望它们能让你相信,Proxy 提供了强大的 Javascript 元编程。

尽管它不像其他ES6功能用的普遍,但 Proxy 有许多用途,包括运算符重载,对象模拟,简洁而灵活的API创建,对象变化事件,甚至Vue 3背后的内部响应系统提供动力。

Proxy 用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”。

ES6原生提供了 Proxy 构造函数,用来生成 Proxy 实例。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">var</span> proxy = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(target, handler);</span>

Proxy 对象的所有用法,都是上面的这种形式。不同的只是 handle 参数的写法。其中 new Proxy 用来生成 Proxy 实例, target 是表示所要拦截的对象, handle 是用来定制拦截行为的对象。

下面是 Proxy 最简单的例子是,这是一个有陷阱的代理,一个 get 陷阱,总是返回 42

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">let</span> target = {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">x</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">10</span>,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">y</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">20</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">};</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">let</span> hanler = {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">get</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">obj, prop</span>) =&gt;</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">42</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">};</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(target, hanler);</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target.x; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">//42</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target.y; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">//42</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target.x; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// 42</span></span>

结果是一个对象将为任何属性访问操作都返回“42”。这包括 target.xtarget['x']Reflect.get(target, 'x') 等。

但是,Proxy 陷阱当然不限于属性的读取。它只是十几个不同陷阱中的一个:

  • handler.get

  • handler.set

  • handler.has

  • handler.apply

  • handler.construct

  • handler.ownKeys

  • handler.deleteProperty

  • handler.defineProperty

  • handler.isExtensible

  • handler.preventExtensions

  • handler.getPrototypeOf

  • handler.setPrototypeOf

  • handler.getOwnPropertyDescriptor

Proxy 用例

默认值/“零值”

在 Go 语言中,有零值的概念,零值是特定于类型的隐式默认结构值。其思想是提供类型安全的默认基元值,或者用gopher的话说,给结构一个有用的零值。

虽然不同的创建模式支持类似的功能,但Javascript无法用隐式初始值包装对象。Javascript中未设置属性的默认值是 undefined 。但 Proxy 可以改变这种情况。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> withZeroValue = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target, zeroValue</span>) =&gt;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(target, {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">get</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">obj, prop</span>) =&gt;</span> (prop <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">in</span> obj ? obj[prop] : zeroValue)</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> });</span>

函数 withZeroValue 用来包装目标对象。如果设置了属性,则返回属性值。否则,它返回一个默认的 “零值”

从技术上讲,这种方法也不是隐含的,但如果我们扩展 withZeroValue ,以Boolean ( false ), Number ( 0 ), String ( "" ), Object ( {} ),Array ( [] )等对应的零值,则可能是隐含的。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">let</span> pos = {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">x</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">4</span>,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">y</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">19</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">};</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">console</span>.log(pos.x, pos.y, pos.z); <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// 4, 19, undefined</span></span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">pos = withZeroValue(pos, <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">0</span>);</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">console</span>.log(pos.z, pos.y, pos.z); <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// 4, 19, 0</span></span>

此功能可能有用的一个地方是坐标系。绘图库可以基于数据的形状自动支持2D和3D渲染。不是创建两个单独的模型,而是始终将 z 默认为  0 而不是 undefined ,这可能是有意义的。

负索引数组

在JS中获取数组中的最后一个元素方式通过写的很冗长且重复,也容易出错。这就是为什么有一个TC39提案定义了一个便利属性 Array.lastItem 来获取和设置最后一个元素。

其他语言,如Python和Ruby,使用负组索引更容易访问最后面的元素。例如,可以简单地使用 arr[-1] 替代 arr[arr.length-1] 访问最后一个元素。

使用 Proxy 也可以在 Javascript 中使用负索引。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> negativeArray = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">els</span> =&gt;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(els, {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">get</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target, propKey, receiver</span>) =&gt;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Reflect</span>.get(</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> target,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> +propKey &lt; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">0</span> ? <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">String</span>(target.length + +propKey) : propKey,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> receiver</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> )</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> });</span>

一个重要的注意事项是包含handler.get的陷阱字符串化所有属性。对于数组访问,我们需要将属性名称强制转换为 Numbers ,这样就可以使用一元加运算符简洁地完成。

现在 [-1] 访问最后一个元素, [-2] 访问倒数第二个元素,以此类推。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> unicorn = negativeArray([<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;:horse:&quot;</span>, <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;:birthday:&quot;</span>, <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;:rainbow:&quot;</span>]);</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">unicorn[<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">-1</span>]; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// ':rainbow:'</span></span>

隐藏属性

众所周知 JS 没有私有属性。  Symbol 最初是为了启用私有属性而引入的,但后来使用像 Object.getOwnPropertySymbols 这样的反射方法进行了淡化,这使得它们可以被公开发现。

长期以来的惯例是将私有属性命名为前下划线 _ ,有效地标记它们“不要访问”。 Prox 提供了一种稍微更好的方法来屏蔽这些属性。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> hide = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target, prefix = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;_&quot;</span></span>) =&gt;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(target, {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">has</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">obj, prop</span>) =&gt;</span> !prop.startsWith(prefix) && prop <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">in</span> obj,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">ownKeys</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">obj</span> =&gt;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Reflect</span>.ownKeys(obj).filter(</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">prop</span> =&gt;</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">typeof</span> prop !== <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;string&quot;</span> || !prop.startsWith(prefix)</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> ),</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">get</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">obj, prop, rec</span>) =&gt;</span> (prop <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">in</span> rec ? obj[prop] : <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">undefined</span>)</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> });</span>

hide 函数包装目标对象,并使得从 in 运算符和 Object.getOwnPropertyNames 等方法无法访问带有下划线的属性。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">let</span> userData = hide({</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">firstName</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;Tom&quot;</span>,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">mediumHandle</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;@tbarrasso&quot;</span>,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">_favoriteRapper</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;Drake&quot;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">});</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">userData._favoriteRapper(</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// undefined</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;_favoriteRapper&quot;</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">in</span> userData</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">); <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// false</span></span>

更完整的实现还包括诸如 deletePropertydefineProperty 之类的陷阱。除了闭包之外,这可能是最接近真正私有属性的方法,因为它们无法通过枚举,克隆,访问或修改来访问。

[图片上传失败...(image-ea9ab5-1564194070930)]

但是,它们在开发控制台中可见。只有闭包才能免于这种命运。

缓存

在客户端和服务器之间同步状态时遇到困难并不罕见。数据可能会随着时间的推移而发生变化,很难确切地知道何时重新同步的逻辑。

Proxy 启用了一种新方法:根据需要将对象包装为无效(和重新同步)属性。所有访问属性的尝试都首先检查缓存策略,该策略决定返回当前在内存中的内容还是采取其他一些操作。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> ephemeral = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target, ttl = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">60</span></span>) =&gt;</span> {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> CREATED_AT = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Date</span>.now();</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> isExpired = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">()</span> =&gt;</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Date</span>.now() - CREATED_AT &gt; ttl * <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">1000</span>;</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">return</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(target, {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">get</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">obj, prop</span>) =&gt;</span> (isExpired() ? <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">undefined</span> : <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Reflect</span>.get(obj, prop))</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> });</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">};</span>

这个函数过于简化了:它使对象上的所有属性在一段时间后都无法访问。然而,将此方法扩展为根据每个属性设置生存时间(TTL),并在一定的持续时间或访问次数之后更新它并不困难。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">let</span> bankAccount = ephemeral(</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">balance</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">14.93</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> },</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">10</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">);</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">console</span>.log(bankAccount.balance); <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// 14.93</span></span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">setTimeout(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">()</span> =&gt;</span> {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">console</span>.log(bankAccount.balance); <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// undefined</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">}, <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">10</span> * <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">1000</span>);</span>

这个示例简单地使银行帐户余额在10秒后无法访问。

枚举和只读视图

这些例子来自 Csaba Hellinge 关于[代理用例][23]和[Mozilla黑客][24]的文章。方法是包装一个对象以防止扩展或修改。虽然 object.freeze`现在提供了将对象渲染为只读的功能,但是可以对这种方法进行扩展,以便访问不存在属性的枚举对象能更好地处理抛出错误。

只读视图

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> NOPE = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">()</span> =&gt;</span> {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">throw</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Error</span>(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;Can't modify read-only view&quot;</span>);</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">};</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> NOPE_HANDLER = {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">set</span>: NOPE,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">defineProperty</span>: NOPE,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">deleteProperty</span>: NOPE,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">preventExtensions</span>: NOPE,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">setPrototypeOf</span>: NOPE</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">};</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> readOnlyView = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target</span> =&gt;</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(target, NOPE_HANDLER);</span>

枚举视图

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> createEnum = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">target</span> =&gt;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> readOnlyView(</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(target, {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">get</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">obj, prop</span>) =&gt;</span> {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">if</span> (prop <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">in</span> obj) {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">return</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Reflect</span>.get(obj, prop);</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> }</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">throw</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">ReferenceError</span>(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">`Unknown prop &quot;<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">${prop}</span>&quot;`</span>);</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> }</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> })</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> );</span>

现在我们可以创建一个 Object ,如果尝试访问不存在的属性现在不是返回 undefined ,而是会抛出异常。这使得在早期捕获和解决问题变得更加容易。

我们的 enum 示例也是代理上的代理的第一个示例,它确认代理是另一个代理的有效目标对象。这通过组合代理功能促进了代码重用。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">let</span> SHIRT_SIZES = createEnum({</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">S</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">10</span>,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">M</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">15</span>,</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">L</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">20</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">});</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">SHIRT_SIZES.S; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// 10</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">SHIRT_SIZES.S = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">15</span>;</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// Uncaught Error: Can't modify read-only view</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">SHIRT_SIZES.XL;</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// Uncaught ReferenceError: Unknown prop &quot;XL&quot;</span>

这种方法可以进一步扩展,包括模拟方法 nameOf ,它返回给定 enum 值的属性名,模仿Javascript等语言中的行为。

虽然其他框架和语言超集(比如TypeScript)提供 enum 类型,但是这个解决方案的独特之处在于,它使用普通Javascript,而不使用特殊的构建工具或转置器。

运算符重载

也许从语法上讲,最吸引人的  Proxy 用例是重载操作符的能力,比如使用handler.has的in操作符。

in 操作符用于检查指定的属性是否位于指定的对象或其原型链中。但它也是语法上最优雅的重载操作符。这个例子定义了一个连续 range 函数来比较数字。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> range = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">min, max</span>) =&gt;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Object</span>.create(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">null</span>), {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">has</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">_, prop</span>) =&gt;</span> +prop &gt;= min && +prop &lt;= max</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> });</span>

与Python不同,Python使用生成器与有限的整数序列进行比较,这种方法支持十进制比较,可以扩展为支持其他数值范围。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> X = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">10.5</span>;</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> nums = [<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">1</span>, <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">5</span>, X, <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">50</span>, <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">100</span>];</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">if</span> (X <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">in</span> range(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">1</span>, <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">100</span>)) {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// true</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// ...</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">}</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">nums.filter(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">n</span> =&gt;</span> n <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">in</span> range(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">1</span>, <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">10</span>)); <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// [1, 5]</span></span>

尽管这个用例不能解决复杂的问题,但它确实提供了干净、可读和可重用的代码。

除了 in 运算符,我们还可以重载 deletenew

cookie对象

如果你曾经与 cookie 进行交互,那么必须处理document.cookie。这是一个不寻常的API,因为API是一个 String ,它读出所有 cookie ,以 分号分隔

document.cookie 是一个看起来像这样的字符串:

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">_octo</span>=GH1.<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">2.2591</span>.<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">47507</span>; _ga=GA1.<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">1.62208</span>.<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">4087</span>; has_recent_activity=<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">1</span></span>

简而言之,处理 document.cookie 比较麻烦且容易出错。一种方法是使用简单的cookie框架,可以适用于使用 Proxy。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> getCookieObject = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">()</span> =&gt;</span> {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> cookies = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">document</span>.cookie</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> .split(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;;&quot;</span>)</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> .reduce(</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">cks, ck</span>) =&gt;</span> ({</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> [ck.substr(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">0</span>, ck.indexOf(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;=&quot;</span>)).trim()]: ck.substr(</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> ck.indexOf(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;=&quot;</span>) + <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">1</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> ),</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> ...cks</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> }),</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> {}</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> );</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> setCookie = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">name, val</span>) =&gt;</span> (<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">document</span>.cookie = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">`<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">${name}</span>=<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">${val}</span>`</span>);</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">const</span> deleteCookie = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">name</span> =&gt;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> (<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">document</span>.cookie = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">`<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">${name}</span>=; expires=Thu, 01 Jan 1970 00:00:01 GMT;`</span>);</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">return</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(cookies, {</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">set</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">obj, prop, val</span>) =&gt;</span> (</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> setCookie(prop, val), <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Reflect</span>.set(obj, prop, val)</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> ),</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">deleteProperty</span>: <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">obj, prop</span>) =&gt;</span> (</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> deleteCookie(prop), <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Reflect</span>.deleteProperty(obj, prop)</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> )</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> });</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">};</span>

此函数返回一个键值对对象,但代理对 document.cookie 进行持久性的所有更改。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">let docCookies = getCookieObject();</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">docCookies.has_recent_activity; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">//</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;1&quot;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">docCookies.has_recent_activity = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;2&quot;</span>; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">//</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;2&quot;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">delete docCookies2[<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;has_recent_activity&quot;</span>]; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">//</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">true</span></span>

在11行代码中,修改 cookie 提供了更好的交互,尽管在生产环境中还需要诸如字符串规范化之类的附加功能。

细节决定成败,Proxy 也不例外。

Polyfill

在撰写本文时(2019年5月),Proxy 没有 完整的 polyfill 。然而,有一个由谷歌编写的 partial polyfill for Proxy ,它支持 getsetapplyconstruct trap ,并适用于IE9+。

它是 Proxy 吗?

确定一个对象是否是代理是不可能的

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

目标是什么?

给定一个代理对象,就不可能获得或更改目标对象。也不可能获取或修改处理程序对象。

最近似的是Ben Nadel的文章Using Proxy to Dynamically Change THIS Binding,它使用一个空对象作为 Proxy 目标和闭包来巧妙地重新分配对象的 Proxy 操作。

Proxy 原语

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">new</span> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">Proxy</span>(<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;To be, or not to be...&quot;</span>, {});</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// TypeError: Cannot create proxy with a non-object as target or handler</span>

不幸的是,Proxy的一个限制是目标必须是Object。这意味着我们不能直接使用像String这样的原语。:disappointed:

性能

Proxy的一个主要缺点是性能。因浏览器和使用而异,但是对于性能有要求的代码来说,代理不是最好的方法。当然,可以衡量影响并确定代理的优势是否超过对性能的影响。

为什么要使用 Proxy?

Proxy 提供虚拟化接口来控制任何目标 Object的行为。这样做可以在简单性和实用性之间取得平衡,而不会牺牲兼容性。

也许使用 Proxy 的最令人信服的理由是,上面的许多示例只有几行,并且可以轻松组合以创建复杂的功能。最后一个例子,我们可以从几个用例中组合函数来创建一个只读 cookie 对象,该对象返回不存在或“私有”隐藏cookie的默认值。

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// document.cookie = &quot;_octo=GH1.2.2591.47507; _ga=GA1.1.62208.4087; has_recent_activity=1&quot;</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">let</span> docCookies = withZeroValue(</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> hide(readOnlyView(getCookieObject())),</span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;Cookie not found&quot;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">);</span>

<span><br /></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">docCookies.has_recent_activity; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// &quot;1&quot;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">docCookies.nonExistentCookie; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// &quot;Cookie not found&quot;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">docCookies._ga; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// &quot;Cookie not found&quot;</span></span>

<span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">docCookies.newCookie = <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">&quot;1&quot;</span>; <span style="max-width: 1000%;box-sizing: border-box !important;word-wrap: break-word !important;">// Uncaught Error: Can't modify read-only view</span></span>

我希望这些例子已经表明,对于Javascript中的小众元编程来说,代理不仅仅是一个深奥的特性。

来源 | https://www.jianshu.com/p/a831f76e5e71

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章