当前位置: 首页 > Web前端 > JavaScript

[翻译]ES6APIs-ReflectandProxy

时间:2023-03-26 22:16:03 JavaScript

ES6中的Reflect和Proxy对象允许开发人员访问以前隐藏在Javascript引擎中的功能。我们在Reflect中广泛使用这些方法,以拦截和重新定义核心WebAPI。Reflect和ProxyReflect和Proxy是ES6规范中的标准内置对象,所有现代浏览器都支持。总的来说,他们通过组合现有的内置API并进一步扩展它们,在Javascript的上下文中形式化了元编程的定义。在本文中,我们使用一些接近现实世界需求的示例来探索它们的使用方式。前言Javascript引擎具有内置方法,例如[[GetOwnProperty]]、[[HasProperty]]和[[Set]],这些方法在标准的早期版本中公开。如果您以前使用过Javascript,您可能熟悉一些开发人员使用的替代方案。例如:constfoo={firstName:'SomeFirstName',age:99}Object.defineProperty(foo,'lastName',{value:'SomeLastName',enumerable:true})constbar=Object.keys(foo)//['firstName','age','lastName']constbaz=Object.values(foo)//['SomeFirstName',99,'SomeLastName']Object.hasOwnProperty.call(foo,'lastName')//true上面的例子演示了一个定义在全局对象上的静态内置方法。它们仅代表我们要使用的一小部分通用引擎内部方法,它们附加在原型上。Reflect和ProxyAPI一起统一并简化了这些现有方法,扩展了内部检查功能,并公开了以前不可能实现的交互API。在本文中,我们将重点关注我们在Reflect中最常用的函数,而不是讨论在这些对象上定义的每个函数。要了解更多关于Reflect的功能,我们建议阅读MDN指南。Reflect的简单例子假设有这样一个场景,每次访问全局对象中的某个字段时,都需要打印一些信息。在整个应用程序中,每次访问对象实例时,您都可以手动调用get()方法并输出信息...//app.ts//在pageload上,我们获取全局sessionwindow.globalSession=fetchSession()//file1。ts//我们已经访问了globalSession上的一个字段,并且开发人员已经记录了constfirstName=globalSession.firstNameconsole.log('GOTFIELDfirstName')//file2.ts//同样适用于这里constlastName=globalSession.lastNameconstage=globalSession.ageconstfirstRelative=globalSession.relatives[0]console.log('GOTFIELDlastName')console.log('GOTFIELDage')console.log('GOTFIELDrelatives[0]')这种模式有缺陷,出于以下几个原因:它需要专有知识:开发人员有责任记住每次访问globalSession上的字段时,还必须调用console.log()方法。这很难执行,也很容易忘记。它不可扩展:如果globalSession上的字段名称发生变化,重构将是一场噩梦。如果您希望对globalSession以外的某个对象实施相同的策略,则需要重复整个原始过程并进一步扩展在代码库中开发所需的知识。它不允许更复杂的场景:上面的示例演示了简单的访问模式,但是当您遇到如下情况时会发生什么?//file3.ts//将另一个global指向全局sessionwindow.activeSession=globalSession//file4.ts//别忘了activeSession和globalSession指向同一个对象,还需要调用console.log()!上述方法中的constmiddleName=activeSession.middleName缺陷说明了我们试图表达的内容与我们如何实现解决方案之间的脱节。每次访问对象上的字段时,我们都希望将一些信息记录到控制台。我们通过强制手动调用函数来解决这个问题。Proxy对象恰好是我们需要的解决方案。这是一个示例://makeStoreAccessProxy.tsconstmakeStoreAccessProxy=(obj:Object)=>{returnnewProxy(obj,{get(target,key,receiver){console.log(`GOTFIELD${key}`)returnReflect.get(target,key)},})}//app.tswindow.globalSession=makeStoreAccessProxy(fetchSession())每次(直接或间接)访问globalSession上的任何字段,访问都会自动记录到安慰。上述方法解决了这个缺陷:不需要专有知识:开发人员可以访问globalSession上的字段,而无需记住存储有关所述访问的信息。它可以扩展:重构globalSession与重构任何其他对象一样简单,并且可以在整个代码库中随时对任何对象使用相同的makeStoreAccessProxy函数。适用于更复杂的场景:如果通过其他指向globalSession的对象获取到globalSession上的某个字段,访问仍然会记录到控制台。请注意,我们利用代理和反射API来实现所需的结果。我们将一一回顾:constmakeStoreAccessProxy=(obj:Object)=>{//此函数返回所提供的“obj”的代理。在不定义第二个//'handler'参数的情况下,这是对'obj'的透明传递,并且会像//尽管它_是_原始'obj'。returnnewProxy(obj,{//然后我们在处理程序中定义一个“get”函数。这意味着我们正在重新定义//对“obj”的基本获取操作get(target,key,receiver){//我们改进“get”以在控制台中记录信息console.log(`GOTFIELD${key}`)//最后,我们在原始未包装的“obj”上调用“get”。return'target[key]',但这证明了//Proxy和ReflectAPI之间的一致性returnReflect.get(target,key)}})}Proxy构造函数中第二个参数handler的get()方法是一致的使用Reflect对象的get()方法。您在Proxy处理程序中定义的每个方法在Reflect对象上都有一个对应的方法。你可以创建一个完全没有意义的Proxy,它只是充当参数传递的角色,覆盖每个支持的方法,简单地调用相应的Reflect方法。constp=newProxy({},{defineProperty(){returnReflect.defineProperty(...arguments)},getPrototypeOf(){returnReflect.getPrototypeOf(...arguments)},get(){returnReflect.get(...arguments)},set(){returnReflect.set(...arguments)},...//etc})ReflectAdvancedExample在这个例子中,我们编写了需要跟踪所有动态加载的代码图片。由于我们不能直接操作底层应用程序代码,我们需要一些机制来透明地捕获对src属性的访问...//首先我们将存储对//HTMLImageElement的src字段的原始属性描述符的引用constoriginalImgSrc=Reflect.getOwnPropertyDescriptor(HTMLImageElement.prototype,'src')//然后我们将覆盖HTMLImageElement原型的“src”属性并捕获//调用该字段的get()和set()方法Reflect.defineProperty(HTMLImageElement.prototype,'src',{get(){//当.src在任何地方被调用时,我们将记录一些信息,然后调用目标的get()方法也使用ReflectAPIconsole.log('gettingthesrc')returnReflect.apply(originalImgSrc.get,this,[])},set(value){//当.src='something'在任何地方被调用时,我们将记录一些信息,然后调用//目标的set()方法也使用ReflectAPIconsole.log(`settingsrcto${value}`)returnReflect.apply(originalImgSrc.set,this,[值])},})从应用程序的角度来看,此更改是透明的,并且可以操纵任何节点的src属性,就好像此覆盖不存在一样。我们只是拦截对这些字段的访问,采取一些行动,然后继续,就好像什么也没发生一样。底层应用程序不需要知道这个变化并且在功能上保持不变。代理示例我们如何使用代理对象?我们可能需要在某些库或框架内部设置捕获行为,以便完全重新定义它们。让我们想象一个场景,其中一个框架有两个内部方法来操作DOM。这两种方法实现相同的最终结果,但一种是异步的而另一种不是。出于性能考虑,异步版本可能是更好的选择,但为了更准确地跟踪用户操作,如果开发者仅使用同步方式,我们建议使用异步方式。有了Proxy,这就不是问题了,我们完全可以自己解决这个问题,而不需要应用程序更改自己的源代码。constsomeFramework=document.querySelector('#framework-root').frameworksomeFramework.blockingUpdate=newProxy(someFramework.blockingUpdate,{apply(target,thisArg,argArray){//每当调用blockingUpdate()console.log('InterceptedacalltoblockingUpdate()')Reflect.apply(target,thisArg,argArray)},})someFramework.asyncUpdate=newProxy(someFramework.asyncUpdate,{apply(target,thisArg,argArray){//在这里我们将重新定义对asyncUpdate()的调用以代替调用blockingUpdate()Reflect.apply(someFramework.blockingUpdate,thisArg,argArray)},})非常周到重要。一般来说,Web应用程序不应该重新定义核心WebAPI(我们认为Reflect的用例是一个例外),但是当代理和Reflect是完成这项工作的正确工具时,了解它们如何工作也很重要。例如,过去我们使用Reflect.defineProperty函数来重新定义存在于网络上许多站点上的全局第三方属性,但是当我们这样做时,我们忘记了包括enumerable:true字段。一个站点特别依赖于可枚举属性,因此当我们重新定义它时,他们站点上的某些功能在使用Reflect应用程序的上下文中停止工作。Reflect(Application)可以被认为是一个自上而下的反射Web应用程序容器,理想情况下对Web应用程序的观察和操作是透明的。如果您想了解更多关于Reflect的工作原理,我们很乐意听取您的意见!您可以通过info@reflect.run联系我们。祝你测试愉快!