共计 5706 个字符,预计需要花费 15 分钟才能阅读完成。
ES6 中的 Reflect 和 Proxy 对象容许开发人员拜访以前暗藏在 Javascript 引擎外部的性能。咱们宽泛地在 Reflect 应用这些办法,为了拦挡和从新定义外围的 web APIs。
Reflect 和 Proxy
Reflect 和 Proxy 在 ES6 标准中均是规范的内置对象,并且被所有的古代浏览器所反对。一般来说,它们在 Javascript 上下文里把元编程的定义正式化了,通过联合现有的内置 APIs,并进一步扩大。在这篇文章中,咱们应用了一些靠近事实需要的例子,来摸索它们是怎么应用。
前言
Javascript 引擎中存在一些内置办法比方 [[GetOwnProperty]], [[HasProperty]], and [[Set]], 这些办法在早前版本的规范中曾经被裸露。如果您以前应用过 Javascript,那么您可能对一些开发人员应用的代替办法很相熟。例如:
const foo = {firstName: 'SomeFirstName', age: 99}
Object.defineProperty(foo, 'lastName', { value: 'SomeLastName', enumerable: true})
const bar = Object.keys(foo) // ['firstName', 'age', 'lastName']
const baz = Object.values(foo) // ['SomeFirstName', 99, 'SomeLastName']
Object.hasOwnProperty.call(foo, 'lastName') // true
下面的示例演示了在全局对象上定义的动态内置办法。它们只代表了一小部分咱们想要应用的罕用的引擎外部办法,并且它们被附加到原型上。总之,Reflect 和 Proxy API 对立并简化了这些现有办法,扩大了外部查看性能,并公开了以前不可能实现的交互 API。
在本文中,咱们将重点介绍咱们在 Reflect 中最罕用的函数,而不是探讨在这些对象上定义的每个函数。要理解更多对于 Reflect 的函数,咱们倡议浏览 MDN 指南。
Reflect 简略例子
如果有一个场景,你须要在每次拜访全局对象里的字段时打印一些信息。你可能会在整个应用程序中在每次拜访对象实例时手动地调用 get() 办法而后输入信息 …
// app.ts
// On pageload, we fetch the global session
window.globalSession = fetchSession()
// file1.ts
// We've accessed a field on globalSession, and the developer has logged that
const firstName = globalSession.firstName
console.log('GOT FIELD firstName')
// file2.ts
// Same applies here
const lastName = globalSession.lastName
const age = globalSession.age
const firstRelative = globalSession.relatives[0]
console.log('GOT FIELD lastName')
console.log('GOT FIELD age')
console.log('GOT FIELD relatives[0]')
这个模式存在缺点,有以下几个起因:
- 它须要专有常识:开发人员负责记住在每次拜访 globalSession 上的某个字段时,还必须调用 console.log() 办法。这很难执行,也很容易遗记。
- 它不具扩展性:如果 globalSession 上的字段名称产生更改时,重构将是一场噩梦。如果您心愿对 globalSession 以外的某些对象实现雷同的策略,则须要反复整个原始过程,并进一步扩大在代码库中开发所需的专有常识。
- 它没有思考到更简单的场景:下面的示例演示了简略的拜访模式,然而当你有如下状况时会产生什么?
// file3.ts
// Point another global to the global session
window.activeSession = globalSession
// file4.ts
// Don't forget that activeSession points to the same object as globalSession, you still need to call console.log()!
const middleName = activeSession.middleName
上述办法中的缺点阐明了咱们试图表白的内容与咱们如何实现解决方案之间的脱节。咱们心愿在每次拜访某个对象上的字段时将一些信息记录到控制台。咱们通过强制手动调用函数来解决这个问题。
Proxy 对象恰好是咱们须要的解决办法。以下是例子:
// makeStoreAccessProxy.ts
const makeStoreAccessProxy = (obj: Object) => {
return new Proxy(obj, {get(target, key, receiver) {console.log(`GOT FIELD ${key}`)
return Reflect.get(target, key)
},
})
}
// app.ts
window.globalSession = makeStoreAccessProxy(fetchSession())
每次(间接或间接)拜访 globalSession 上的任何字段时,该拜访将自动记录到控制台。
上述办法解决了缺点:
- 不须要专有常识:开发人员能够拜访 globalSession 上的字段,而无需记住存储无关所述拜访的信息。
- 它能够扩大:重构 globalSession 与重构任何其余对象一样简略,并且雷同的 makeStoreAccessProxy 函数能够随时用于整个代码库中的任何对象。
- 它实用更简单的场景:如果通过指向 globalSession 的其余对象获取 globalSession 上的某个字段,则拜访仍将记录到控制台。
请留神,咱们利用了代理和反射 API 来实现所需的后果。咱们将逐个回顾这一点:
const makeStoreAccessProxy = (obj: Object) => {
// This function returns a proxy of the provided 'obj'. Without defining the second
// 'handler' argument, this is a transparent passthrough to 'obj' and would behave as
// though it _were_ the original 'obj'.
return new Proxy(obj, {
// We then define a 'get' function in the handler. This means that we're redefining
// the fundamental get operation on 'obj'
get(target, key, receiver) {
// We've redefined'get' to log information in the console
console.log(`GOT FIELD ${key}`)
// And finally, we're calling'get'on the original unwrapped'obj'. We could
// instead return 'target[key]', but this demonstrates the consistency between
// the Proxy and Reflect APIs
return Reflect.get(target, key)
}
})
}
Proxy 构造函数中的第二个参数 handler 的 get() 办法和 Reflect 对象的 get() 是保持一致的。你在 Proxy handler 中定义的每一个办法都有一个对应的办法在 Reflect 对象中。您能够创立一个齐全没有意义的 Proxy,它仅充当一个传递参数的角色来重写每一个反对的办法并简略的调用对应的 Reflect 办法。
const p = new Proxy({}, {defineProperty() {return Reflect.defineProperty(...arguments) },
getPrototypeOf() { return Reflect.getPrototypeOf(...arguments) },
get() { return Reflect.get(...arguments) },
set() { return Reflect.set(...arguments) },
... // etc
})
Reflect 高级例子
在这个例子中,咱们编写的代码是须要跟踪页面上所有的动静加载的图像。因为咱们不能间接操作底层利用程序代码,所以咱们须要某种机制来通明地捕捉对 src 属性的拜访…
// First we'll store a reference to the original property descriptor for the
// HTMLImageElement's src field
const originalImgSrc = Reflect.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src')
// Then we'll overwrite the HTMLImageElement prototype's "src" property and trap
// calls to that field's get() and set() methods
Reflect.defineProperty(HTMLImageElement.prototype, 'src', {get() {
// When <someImg>.src is called anywhere, we'll log some information, then call the
// target's get() method also using the Reflect API
console.log('getting the src')
return Reflect.apply(originalImgSrc.get, this, [])
},
set(value) {
// When <someImg>.src = 'something' is called anywhere, we'll log some information, then call the
// target's set() method also using the Reflect API
console.log(`setting src to ${value}`)
return Reflect.apply(originalImgSrc.set, this, [value])
},
})
从应用程序的角度来看,这个变动是通明的。任何 <img> 节点的 src 属性都能够像不存在此笼罩一样进行操作。咱们只是截取对这些字段的拜访,采取一些口头,而后持续进行,就如同什么都没产生一样。底层应用程序不须要晓得这种变动,并且在性能上放弃不变。
Proxy 例子
咱们是怎么应用 Proxy 对象?咱们可能须要在某些库或框架的外部设置捕捉行为,以便齐全从新定义它们。让咱们构想一个场景,其中一个框架有两个外部办法来操作 DOM。两种办法都实现了雷同的最终后果,但一种是异步的,而另一种不是。思考到性能起因,异步的版本可能是更好的抉择,但为了更准确地跟踪用户的操作,咱们更举荐异步的办法,如果开发者仅应用了同步的办法。
有了 Proxy,这不是一个问题,咱们能够齐全本人解决这个问题,而不须要应用程序更改本人的源代码。
const someFramework = document.querySelector('#framework-root').framework
someFramework.blockingUpdate = new Proxy(someFramework.blockingUpdate, {apply(target, thisArg, argArray) {// Here we'll take some action whenever a call to blockingUpdate() is made
console.log('Intercepted a call to blockingUpdate()')
Reflect.apply(target, thisArg, argArray)
},
})
someFramework.asyncUpdate = new Proxy(someFramework.asyncUpdate, {apply(target, thisArg, argArray) {// Here we'll redefine calls to asyncUpdate() to instead invoke blockingUpdate()
Reflect.apply(someFramework.blockingUpdate, thisArg, argArray)
},
})
总结
在应用本文形容的 API 时,考虑周到是很重要的。一般来说,web 应用程序不应该从新定义外围 web API(咱们认为 Reflect 的用例是一个例外),然而当代理和 Reflect 是正确的工作工具时,理解它们的工作形式也很重要。例如,在过来,咱们应用 Reflect.defineProperty 函数来从新定义存在于 web 上许多站点上的全局第三方属性,然而当咱们这样做时,咱们遗记了蕴含 enumerable:true 字段。特地是一个站点依赖于可枚举的属性,因而当咱们从新定义它时,他们站点上的一些性能在应用 Reflect 应用程序的上下文中进行工作。
Reflect(应用程序)能够被认为是一个自上而下的反射 web 应用程序容器,现实状况下,它对 web 应用程序的察看和操作是通明的。如果您想理解更多对于 Reflect 如何工作的信息,咱们很乐意听到您的音讯!你能够在 info@reflect.run 分割咱们。高兴地测试吧!