反射对象在 JavaScript 中做什么?

前段时间我在 MDN 上看到一个空白的存根,是关于 javascript 中的 Reflect对象的,但是我一辈子都找不到任何关于 Google 的信息。今天我发现了这个 http://people.mozilla.org/~jorendorff/es6-draft.html#sec-reflect-object,除了领域和加载程序功能之外,它听起来与 Proxy 对象类似。

基本上,我不知道我找到的这个页面是否只解释了如何实现反射,或者我只是不能理解它的措辞。有没有人能给我大致解释一下 Reflect的方法是做什么的?

例如,在我发现的页面上说,调用 Reflect.apply ( target, thisArgument, argumentsList ) 将“返回调用 target 的[[ Call ]]内部方法的结果,其中包含 this 参数和 args。”但这和打电话给 target.apply(thisArgument, argumentsList)有什么区别呢?

更新:

Thanks to @Blue, I found this page on the wiki http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api&s=reflect 据我所知,它说反射对象提供了所有动作的方法版本,这些动作可以被代理捕获,从而使转发更容易。但我觉得这有点奇怪因为我觉得这没有必要。但它似乎做得更多一点,特别是标准,表示 double-lifting,但这指向旧的代理规范/

26872 次浏览

根据在维基上找到的草案文件,

Http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts

我们得到了草案中澄清的关于“单个普通对象”的一行,它还有函数定义。

维基应该是可靠的,因为你可以找到一个链接到它从 emcascript 网站

Http://www.ecmascript.org/dev.php

我发现第一个链接的谷歌虽然没有任何运气找到它通过搜索维基直接。

UPDATE 2015: As pointed out by 第七名's 回答, now that ES6 (ECMAScript 2015) has been finalized, more appropriate documentation is now available:


原答案(关于(历史)理解和额外例子) :

Reflection proposal似乎已经进展到 ECMAScript 6规范草案。这个文档目前概述了 Reflect-对象的方法,并且只对 Reflect-对象本身说明了以下内容:

反射对象是一个单独的普通对象。

反射对象的[[ Prototype ]]内部槽的值是标准的内置 Object 原型对象(19.1.3)。

反射对象不是函数对象。它没有[[构造]]内部方法; 不可能使用反射对象作为具有 新的操作符的构造函数。反射对象也没有[[ Call ]]内部方法; 不可能将反射对象作为函数调用。

然而,在 ES 和谐中有一个简短的解释:

“@response”模块有多种用途:
  • 现在我们已经有了模块,对于以前在 Object 上定义的许多反射方法来说,“@response”模块是一个更自然的位置。 出于向后兼容的目的,Object 上的静态方法不太可能消失。但是,新方法可能应该添加到“@response”模块,而不是 Object 构造函数。
  • 代理的天然家园,避免了对全局代理绑定的需要。
  • 此模块中的大多数方法都将一对一映射到代理陷阱。代理处理程序需要这些方法来方便地转发操作,如下所示。



因此,Reflect对象提供了许多实用函数,其中许多函数似乎与全局 Object 上定义的 ES5方法重叠。

但是,这并不能真正解释这个程序打算解决的现有问题或添加的功能。我怀疑这可能是闪烁的,实际上,上述和声规范链接到一个 “这些方法的非规范的、大致的实施”

研究这些代码可以(进一步)了解它们的用途,但幸运的是,还有一个 wiki 概述了 一系列反射对象有用的原因:
(我已经复制(和格式化)以下文本,以备将来从该源引用,因为它们是我可以找到的 只有示例。除此之外,它们还有意义,已经有了很好的解释,并且触及了问题的 apply示例。)


更有用的返回值

Reflect中的许多操作类似于在 Object上定义的 ES5操作,例如 Reflect.getOwnPropertyDescriptorReflect.defineProperty。然而,当成功定义属性时,Object.defineProperty(obj, name, desc)将返回 obj,否则抛出一个 TypeError,而 Reflect.defineProperty(obj, name, desc)被规定只返回一个布尔值,指示是否成功定义了属性。这允许您重构这段代码:

try {
Object.defineProperty(obj, name, desc);
// property defined successfully
} catch (e) {
// possible failure (and might accidentally catch the wrong exception)
}

这样说:

if (Reflect.defineProperty(obj, name, desc)) {
// success
} else {
// failure
}

返回这种布尔成功状态的其他方法有 Reflect.set(更新属性)、 Reflect.deleteProperty(删除属性)、 Reflect.preventExtensions(使对象不可扩展)和 Reflect.setPrototypeOf(更新对象的原型链接)。


一流的行动

在 ES5中,检测对象 obj是否定义或继承某个属性名的方法是编写 (name in obj)。类似地,要删除属性,可以使用 delete obj[name]。虽然专用语法很好,也很简短,但这也意味着当您希望将操作作为第一类值传递时,必须将这些操作显式地包装在函数中。

使用 Reflect,这些操作很容易被定义为一级函数:
Reflect.has(obj, name)的功能等价于 (name in obj),而 Reflect.deleteProperty(obj, name)的功能与 delete obj[name].相同


更可靠的功能应用

在 ES5中,当需要调用一个函数 f,该函数具有数量可变的参数作为数组 args打包,并将 this值绑定到 obj时,可以写:

f.apply(obj, args)

但是,f可能是一个有意或无意地定义自己的 apply方法的对象。当您真正想要确保调用内置的 apply函数时,通常会写:

Function.prototype.apply.call(f, obj, args)

这不仅是冗长的,它很快变得难以理解。使用 Reflect,您现在可以以一种更简短、更容易理解的方式进行可靠的函数调用:

Reflect.apply(f, obj, args)


变量参数构造函数

假设您想调用一个具有可变参数数目的构造函数。在 ES6中,由于新的扩展语法,可以编写如下代码:

var obj = new F(...args)

在 ES5中,这很难写,因为只能使用 F.apply或者 F.call来调用参数数量可变的函数,但是没有 F.construct函数来调用参数数量可变的函数 new。使用 Reflect,你现在可以用 ES5写:

var obj = Reflect.construct(F, args)


代理陷阱的默认转发行为

当使用 Proxy对象来包装现有对象时,拦截一个操作、做一些事情,然后“做默认的事情”是非常常见的,这通常是将被拦截的操作应用到被包装的对象。例如,假设我想简单地记录对一个对象 obj的所有属性访问:

var loggedObj = new Proxy(obj, {
get: function(target, name) {
console.log("get", target, name);
// now do the default thing
}
});

ReflectProxy API were designed in tandem,这样对于每个 Proxy陷阱,在 Reflect上都存在一个相应的方法,它“做默认的事情”。因此,当您发现自己想要在代理处理程序中“执行默认”操作时,正确的做法是始终调用 Reflect对象中的相应方法:

var loggedObj = new Proxy(obj, {
get: function(target, name) {
console.log("get", target, name);
return Reflect.get(target, name);
}
});

Reflect方法的返回类型保证与 Proxy陷阱的返回类型兼容。


控制访问器的 this-bind

在 ES5中,进行通用属性访问或属性更新相当容易:

var name = ... // get property name as a string
obj[name] // generic property lookup
obj[name] = value // generic property update

Reflect.getReflect.set方法允许您做同样的事情,但是另外接受一个 receiver参数作为最后一个可选参数,当您获得/set 的属性是一个访问器时,该参数允许您显式设置 this绑定:

var name = ... // get property name as a string
Reflect.get(obj, name, wrapper) // if obj[name] is an accessor, it gets run with `this === wrapper`
Reflect.set(obj, name, value, wrapper)

这在您包装 obj并希望访问器中的任何自发方式被重新路由到您的包装器时偶尔会很有用,例如,如果 obj定义为:

var obj = {
get foo() { return this.bar(); },
bar: function() { ... }
}

调用 Reflect.get(obj, "foo", wrapper)将导致 this.bar()调用被重新路由到 wrapper


避免遗留 __proto__

On some browsers, __proto__ is defined as a special property that gives access to an object's prototype. ES5 standardized a new method Object.getPrototypeOf(obj) to query the prototype. Reflect.getPrototypeOf(obj) does exactly the same, except that Reflect also defines a corresponding Reflect.setPrototypeOf(obj, newProto) to set the object's prototype. This is the new ES6-compliant way of updating an object's prototype.
Note that: ABC0 存在于 Object (as correctly pointed out by 's 评论)!


EDIT:
旁注(针对问题的注释) : 有一个简短的 关于“问题: ES6模块与 HTML 导入”的回答,它解释了 RealmsLoader对象。

这个链接提供了另一种解释:

领域对象抽象出一个独特的全局环境的概念, 使用它自己的全局对象、标准库的副本,以及 “内部函数”(不绑定到全局变量的标准对象, 比如 Object.model 的初始值)。

可扩展 web : 这是同源的动态等价物 <iframe> without DOM.

但是值得一提的是: all this is still in draft, this is not a specification etched in stone!是 ES6,所以请记住浏览器兼容性!

希望这个能帮上忙!