JAVASCRIPT中的元编程 (METAPROGRAMMING IN JAVASCRIPT)
In the earlier lesson, we learned about some metaprogramming concepts one of which is intercession. Intercession stands for intervening in an operation or intercepting an operation. For example, if you trying to get the value of an object’s property, but that operation was intercepted by some JavaScript program and a false or modified value was returned instead.
在前面的课程中 ,我们了解了一些元编程概念,其中之一是代祷 。 代祷代表干预手术或拦截手术。 例如,如果您尝试获取对象属性的值,但是该操作被某些JavaScript程序拦截,则返回了错误或修改后的值。
Intercession is like a man-in-the-middle attack where data is transformed in-flight between the source and the consumer but done deliberately. The program that sits between the source and the consumer is called Proxy.
调解就像是中间人攻击 ,其中数据在源和使用者之间进行动态转换,但是故意进行的。 位于源和使用者之间的程序称为Proxy 。
You may have heard about this term while connecting to a remote server through a proxy server that sits between you and the remote server. Here the proxy server is able to hide your identity from the remote server as well as transform data received from the remote server if necessary.
通过位于您和远程服务器之间的代理服务器连接到远程服务器时,您可能听说过此术语。 在这里,代理服务器能够从远程服务器隐藏您的身份,并在必要时转换从远程服务器接收到的数据。
JavaScript gives us the Proxy
class in ES2015+ that helps us create a proxy around an object. The Proxy
global object is a constructor function (class) and its constructor signature is as follows.
JavaScript在ES2015 +中为我们提供了Proxy
类,可帮助我们围绕对象创建代理。 Proxy
全局对象是构造函数( class ),其构造函数签名如下。
var proxy = new Proxy(target, handler);
Here, the target
is the object for which a proxy needs to be created. The proxy
is the object that will be used to perform some operations on the target
. For example, proxy.prop = 1
operation would be handled by the handler
and its job is to set the value of target.prop
to 1
.
在此, target
是需要为其创建代理的对象。 proxy
是将用于对target
执行某些操作的target
。 例如, proxy.prop = 1
操作将由被处理handler
和它的工作是的值设置target.prop
到1
。
💡 Both
target
andhandler
objects are required and must be descendents ofObject
and not a primitive value (includingnull
andundefined
). Else,TypeError: Cannot create proxy with a non-object as target or handler
error is thrown.target
target
和handler
对象都是必需的,并且必须是Object
后代,而不是原始值(包括null
和undefined
)。 否则,TypeError: Cannot create proxy with a non-object as target or handler
错误引发,因此TypeError: Cannot create proxy with a non-object as target or handler
。
Creating a proxy around target
doesn’t prevent one from accessing the target
but it should be kept private. The handler
object contains specific methods one of which will be invoked by JavaScript when a certain operation is performed on the proxy
and that method would be responsible to communicate with the target
.
在target
周围创建代理不会阻止访问target
但应将其保持私有状态。 handler
对象包含特定的方法,当对proxy
执行特定操作时,其中的一种方法将由JavaScript调用,并且该方法将负责与target
进行通信。
In the Reflect lesson, we learned about the internal slots and internal methods. Every proxy
object has two internal slots viz. [[ProxyTarget]]
and [[ProxyHandler]]
. When we create a proxy
object with new Proxy()
, these slots are filled with target
and handler
arguments respectively.
在“ 反思”课程中,我们了解了内部插槽和内部方法 。 每个proxy
对象都有两个内部插槽,即。 [[ProxyTarget]]
和[[ProxyHandler]]
。 当我们使用new Proxy()
创建proxy
对象时,这些插槽分别填充了target
参数和handler
参数。
In the Reflect lesson, we also learned about the essential internal methods of all objects and saw a table full of internal methods (table above). These internal methods are trigged by Reflect
’s static methods. Guess what, a proxy
object also has these exact same internal methods as shown in the table below. These method signatures are as same as the above table.
在“ 反射”课程中,我们还了解了所有对象的基本内部方法 ,并看到了一个充满内部方法的表 (上表 )。 这些内部方法由Reflect
的静态方法触发。 猜猜什么, proxy
对象也具有这些完全相同的内部方法,如下表所示。 这些方法签名与上表相同。
When the proxy
’s internal method is called, it calls the target
’s internal method with the exact same name. For example, if proxy[[Get]]()
is called when proxy.prop
is accessed (prop
is just a property name), it will trigger target[[Get]]()
method. This is called no-op
forwarding since proxt
’s is not performing any operation of its own.
调用proxy
的内部方法时,它将使用完全相同的名称调用target
的内部方法。 例如,如果在访问proxy.prop
时调用proxy[[Get]]()
( prop
只是一个属性名 ),它将触发target[[Get]]()
方法。 这称为no-op
转发,因为proxt
自身不执行任何操作。
However, we can provide an implementation of the internal method of the proxy
using the handler
. In the right-side column of the above table, we have the method (property) names that will be used to trap the internal method call of the proxy
object. For example, handler.get
method would trap the [[Get]]
internal method call.
但是,我们可以使用handler
proxy
内部方法的实现。 在上表的右侧列中,我们具有方法( 属性 )名称,这些名称将用于捕获 proxy
对象的内部方法调用。 例如, handler.get
方法将捕获[[Get]]
内部方法调用。
These handler methods are called traps as they trap or intercept the native operation of the proxy
, therefore the native operation of the target
. This method receives target
as the first argument and the rest of the arguments are received as per the internal method specification.
这些处理程序方法称为陷阱,因为它们捕获或拦截proxy
的本机操作,因此也就是target
的本机操作。 此方法接收target
作为第一个参数,其余参数根据内部方法规范接收。
If you read the Reflect lesson, then you know that the Reflect
’s static method calls the internal methods of the target
. So the Reflect.get(target, prop, receiver)
calls the target[[Get]](prop, receiver)
internal method which returns the value of the prop
property of the target
object.
如果您阅读了Reflect课,那么您就会知道Reflect
的静态方法将调用target
的内部方法。 因此, Reflect.get( target , prop , receiver )
调用target[[Get]]( prop , receiver )
内部方法,该方法返回target
对象的prop
属性值。
A trap receives arguments as you would call a Reflect
method with the same name. So in this case, handler.get
trap would receive target
, prop
, and receiver
as the argument. Now it is up to you to communicate with the target
and return the result back.
陷阱会接收参数,就像您将调用具有相同名称的Reflect
方法一样。 所以在这种情况下, handler.get
陷阱将收到target
, prop
,以及receiver
作为参数。 现在,您可以与target
进行通信并将结果返回。
This also means that you can call an equivalent Reflect
method from within a trap and pass all the arguments to the Reflect
method without even looking. This is the reason why Reflect
static method and Proxy
’s handler methods (traps) share the same method signature.
这也意味着您可以从陷阱中调用等效的Reflect
方法,并将所有参数传递给Reflect
方法,而无需查看。 这就是Reflect
静态方法和Proxy
的处理程序方法( 陷阱 )共享相同方法签名的原因。
So if a trap is missing, then internal methods of the proxy
object gets called which calls the internal method of the target
and the proxy would behave as in there is no proxy at all. Meaning proxy.prop
is equivalent to calling Reflect.get(target, prop)
which is somewhat equivalent to target.prop
.
因此,如果缺少陷阱,则会调用proxy
对象的内部方法,该方法将调用target
的内部方法,并且代理的行为将与根本没有代理一样。 含义proxy.prop
等效于调用Reflect.get( target , prop )
,后者在某种程度上等效于target.prop
。
If a trap is provided on the handler
, you intercept the operation on the target
such as if handler.get
is present, so proxy.prop
would execute the handler.get
method and you can communicate with the target
using a native approach or use the Reflect.get
.
如果被设置在一个陷阱handler
,则拦截对操作target
诸如如果handler.get
存在,所以proxy.prop
将执行handler.get
方法和可以与所述通信target
使用本机方法或使用Reflect.get
。
Therefore, for every Reflect
static method name, there is an equivalent trap for the handler
. You can just go through the Reflect lesson and understand the method signature of the Reflect
’s static method because these would be method signature of the traps available in the handler
. So we are not gonna go through them in this lesson and jump straight to examples.
因此,对于每个Reflect
静态方法名称,该handler
都有一个等效的陷阱。 您只需要阅读Reflect的课程,并了解Reflect
的静态方法的方法签名,因为这些将是handler
可用陷阱的方法签名。 因此,我们在本课程中不会通过它们来直接学习示例。
💡 You can find the list of all supported methods on this MDN documentation but it would be the same as Reflect’s static method signatures.
get
和set
陷阱 (The get
and set
traps)
Let’s create a proxy for a simple object that contains name
and age
properties. The name
property contains an object with fname
and lname
properties to represent a person’s name. What we want to achieve make name
property look like it is a string. Therefore when a user accesses it, it will come as a string
and when the user sets a string
value, the string
value will be split between the fname
and lname
properties.
让我们为包含name
和age
属性的简单对象创建代理。 name
属性包含一个对象,该对象具有fname
和lname
属性来表示一个人的名字。 我们要实现的目标是使name
属性看起来像一个字符串。 因此,当用户访问它时,它将作为string
并且当用户设置string
值时,该string
值将在fname
和lname
属性之间分割。
In the above example, we have created a proxy
for the target
object with get
and set
traps. With this, whenever proxy
encounters get
operations using proxy.prop
syntax or Reflect.get(proxy, ...)
, the get
trap would be executed. Same goes for the set
operation.
在上面的示例中,我们使用get
和set
陷阱为target
对象创建了proxy
。 有了这个,只要proxy
的遭遇get
使用操作proxy.prop
语法或Reflect.get( proxy , ...)
在get
陷阱将被执行。 set
操作也是如此。
In the get
trap, we have accessed the property value on the target
returned a transformed response (when prop
is name
). In the case of set
trap, we are updating property value in the target
and returning a boolean
because that’s the signature of the corresponding Reflect.set
method call.
在get
陷阱中,我们已经访问了target
上的属性值,并返回了转换后的响应( 当 prop
为 name
)。 在set
陷阱的情况下,我们将更新target
属性值并返回一个boolean
因为这是相应Reflect. set
的签名Reflect. set
Reflect. set
方法调用。
💡 You can also use
{}
as thehandler
value. In this case, none of thetarget
operations will be intercepted by the proxyhandler
and it will be directly processed by internal methods of theproxy
.also您也可以使用
{}
作为handler
值。 在这种情况下,没有任何的target
运行将由代理截获handler
,它会通过内部方法直接处理proxy
。
From the logs, you can see that whenever we assigned a value to proxy
, the respective set
trap was called. Since there is no has
trap on the handler
, the "age" in proxy
operation (or Reflect.has(proxy, "age")
) would be carried out on the target
without any intercession.
从日志中可以看到,每当我们为proxy
分配一个值时,就会调用相应的set
陷阱。 由于没有has
陷阱handler
中, "age" in proxy
操作( 或 Reflect. has (proxy, "age")
将是对执行target
没有任何说情。
preventExtensions
陷阱 (The preventExtensions
trap)
Using a proxy, you can prevent the target
object getting injected with arbitrary properties. For this, we need to use the preventExtensions
trap.
使用代理,可以防止target
对象被注入任意属性。 为此,我们需要使用preventExtensions
陷阱。
In the above example, preventExtensions
trap is called when Object.preventExtensions
or Reflect.preventExtensions
method is called on the proxy
. Through this trap, we are freezing the target
using Object.freeze()
method (Object.seal
) which will prevent the addition of any new properties to the target.
在上面的示例中,当Object.preventExtensions
或Reflect. preventExtensions
时,将调用preventExtensions
陷阱Reflect. preventExtensions
在proxy
上调用了Reflect. preventExtensions
方法。 通过这个陷阱,我们使用Object. freeze ()
冻结target
Object. freeze ()
Object. freeze ()
方法( Object. seal
),这将防止添加任何新的属性到目标的。
Though this works, it would change the behavior of the target
as you can see in the above example, the target.salary
operation was unsuccessful as the target
was frozen by the proxy in an earlier call. I would recommend adding some logic inside the set
trap to prevent any new property addition when proxy
is non-extensible instead of mutating the target
object.
尽管此方法有效,但可以更改target
的行为,如您在上例中target.salary
, target.salary
操作未成功,因为target
在较早的调用中已被代理冻结。 我建议在set
陷阱内添加一些逻辑,以防止proxy
不可扩展时添加任何新属性,而不是使target
对象发生变异。
construct
陷阱 (The construct
trap)
You can use proxy as a means to enforce a singleton pattern.
您可以使用代理来强制执行单例模式。
In the above example, we have created the PersonProxy
for the Person
class (constructor function) and we are intercepting the instantiation operation using the construct
trap. Hence whenever new PersonProxy()
call is made or Reflect.construct(PersonProxy, ...)
call is made, construct
trap is executed which would pass all the control to Reflect.construct
.
在上面的示例中,我们为Person
类创建了PersonProxy
( 构造函数 ),并使用construct
陷阱拦截了实例化操作。 因此,无论何时进行new PersonProxy()
调用或Reflect. construct ( PersonProxy , ...)
Reflect. construct ( PersonProxy , ...)
调用,执行construct
陷阱,该陷阱会将所有控件传递给Reflect.construct
。
可撤销代理 (Revocable proxy)
A revocable proxy is an object containing proxy
field which contains the actual proxy and revoke
field which is a function which when called sets the the [[ProxyTarget]]
and [[ProxyHandler]]
internal slots of the proxy
to null
. Therefore, any further operations on the proxy
would result in a TypeError
.
可撤销代理是一个包含proxy
字段的对象,该对象包含实际的代理和revoke
字段,该字段是一个函数,当调用该函数时, [[ProxyHandler]]
proxy
的[[ProxyTarget]]
和[[ProxyHandler]]
内部插槽设置为null
。 因此,对proxy
任何进一步的操作都将导致TypeError
。
var { proxy, revoke } = Proxy.revocable(target, handler);
The Proxy.revocable
method returns a revocable proxy object. A revocable proxy would be ideal when a third-party API needs the access of target
but you want to intercept operations performed by this third-party API on the target
. In that case, you can only provide the proxy
object.
Proxy. revocable
Proxy. revocable
方法返回可撤销代理对象。 当第三方API需要访问target
但您想拦截此第三方API在target
上执行的操作时,可撤销代理将是理想的选择。 在这种情况下,您只能提供proxy
对象。
This abstracts the target
object from the third-part API. Once you do not want this third-part API to make any more changes to the target
, you can just call the revoke
method. Any further operations on the proxy
would then result in a TypeError
error.
这将从第三方API中提取target
对象。 一旦您不希望此第三方API对target
进行更多更改,就可以调用revoke
方法。 proxy
上的任何进一步操作都将导致TypeError
错误。
As you can see from the above example, once revoke
has been called, any further operations on the proxy
would result in a TypeError
exception, whether or not that operation has a trap in the handler
.
从上面的示例可以看到,一旦调用了revoke
,对proxy
任何进一步操作都将导致TypeError
异常,无论该操作是否在handler
有陷阱。
翻译自: https://medium.com/jspoint/introduction-to-proxy-api-for-metaprogramming-in-javascript-fa2781e360ba