【问题标题】:What does the Reflect object do in JavaScript?Reflect 对象在 JavaScript 中的作用是什么?
【发布时间】:2014-10-14 19:48:33
【问题描述】:

不久前,我在 MDN 上看到一个空白存根,用于 javascript 中的 Reflect 对象,但我终其一生都无法在 Google 上找到任何东西。今天我发现了这个http://people.mozilla.org/~jorendorff/es6-draft.html#sec-reflect-object,除了领域和加载器功能之外,它听起来类似于代理对象。

基本上,我不知道我找到的这个页面是否只解释了如何实现 Reflect 或者我只是无法理解它的措辞。有人可以向我解释一下Reflect的方法是什么吗?

例如,在我发现的页面上说调用Reflect.apply ( target, thisArgument, argumentsList ) 将“返回使用参数 thisArgument 和 args 调用目标的 [[Call]] 内部方法的结果。”但这与仅仅打电话给target.apply(thisArgument, argumentsList) 有什么不同?

更新:

感谢@Blue,我在 wiki 上找到了这个页面 http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api&s=reflect 据我所知,反射对象提供了可以被代理捕获的所有操作的方法版本,以使转发更容易。但这对我来说似乎有点奇怪,因为我不明白它是如何完全有必要的。但它似乎做的不止于此,尤其是上面写着double-lifting 但指向旧代理规范/

【问题讨论】:

  • 规范说“反射对象是一个普通的对象。”,据我了解,Reflect 只是RealmLoader 对象的容器,但我不知道是什么后者也可以。
  • 谢谢:),我似乎从我链接到的页面(不知道它有多合法)每个领域都是它自己的“java 脚本上下文”,并且加载器加载领域像模块或一些东西,基于反射和代理之间的相似性以及内置功能的代理类型“重载”Reflect.LoaderReflect.Realm 与重载模块功能有关?
  • 看起来它是一个带有静态方法的“静态类”(如 JSON):isExtensibleownKeys 等。在 ES 6 中,使用实际类,这对于了解更多关于类(target 我认为是16.1.2)。

标签: javascript ecmascript-6


【解决方案1】:

2015 年更新: 正如7th's answer 所指出的,现在 ES6 (ECMAScript 2015) 已经完成,现在可以获得更合适的文档:


原始答案(用于(历史)理解和额外示例)

Reflection proposal 似乎已经升级为Draft ECMAScript 6 Specification。本文档目前概述了Reflect-object 的方法,仅说明了Reflect-object 本身的以下内容:

Reflect 对象是一个普通的对象。

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

Reflect 对象不是函数对象。它没有 [[Construct]] 内部方法;不能将 Reflect 对象用作带有 new 运算符的构造函数。 Reflect 对象也没有 [[Call]] 内部方法;不能将 Reflect 对象作为函数调用。

不过,在ES Harmony 中有一个关于其用途的简短说明:

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



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

但是,这并不能真正解释它打算解决哪些现有问题或添加了哪些功能。我怀疑这可能会被填充,实际上,上面的和谐规范链接到'non-normative, approximate implementation of these methods'

检查该代码可以(进一步)了解它的用途,但幸运的是还有一个 wiki 概述了 a number of reasons why the Reflect object is useful
(我已经复制(并格式化( /sub>


更多有用的返回值

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.applyF.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 是串联设计的,因此对于每个Proxy 陷阱,在Reflect 上存在一个“执行默认操作”的相应方法。因此,每当您发现自己想要在 Proxy 处理程序中“执行默认”操作时,正确的做法是始终调用 Reflect 对象中的相应方法:

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

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


控制访问器的 this 绑定

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

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

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

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__

在某些浏览器上,__proto__ 被定义为一个特殊属性,可以访问对象的原型。 ES5 标准化了一种新方法Object.getPrototypeOf(obj) 来查询原型。 Reflect.getPrototypeOf(obj) 的作用完全一样,只是Reflect 还定义了一个对应的Reflect.setPrototypeOf(obj, newProto) 来设置对象的原型。这是更新对象原型的符合 ES6 的新方法。
请注意:setPrototypeOf also exists on Object(正如Knucomment 正确指出的那样)!


编辑:
旁注(将 cmets 指向 Q):有一个简短的 answer on 'Q: ES6 Modules vs. HTML Imports' 解释了 RealmsLoader 对象。

this link提供了另一种解释:

领域对象抽象了一个独特的全局环境的概念, 拥有自己的全局对象、标准库的副本,以及 “内在”(未绑定到全局变量的标准对象, 像 Object.prototype 的初始值)。

可扩展网络:这是同源的动态等价物 <iframe> 没有 DOM。

值得一提的是:这一切都还在草稿中,这不是一成不变的规范! 它是 ES6,所以请牢记浏览器兼容性!

希望这会有所帮助!

【讨论】:

  • @Spencer Killen:嗯.. 这个答案是否将您的想法指向了正确的方向并解释了这与Reflect.applytarget.apply 之间的区别有何关​​系?或者我应该在赏金结束前添加什么?
  • setPrototypeOf 也存在于Object
  • 请注意,如果您使用原型属性代理对象,则使用Reflect.get 作为代理获取的默认实现不能很好地工作。它只是抱怨它不起作用。但是,如果您使用 Reflect.get(target, property) 而不通过 receiver,那么它确实有效。
  • 我的测试总是通过代理访问属性,导致target 是代理包装的原始目标,而receiver 是代理本身。但是,如果您设法以不同的方式访问属性,这可能会有所不同。
  • 这是一个很棒的答案!谢谢
【解决方案2】:

按照 wiki 上的草稿文档,

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

我们得到了草案中阐明的“单一普通对象”这一行。它也有函数定义。

wiki 应该是可靠的,因为您可以从 emcascript 网站找到指向它的链接

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

虽然我通过 google 找到了第一个链接,但没有任何运气通过直接搜索 wiki 找到它。

【讨论】:

  • 谢谢,官方规范中列出的每个方法被调用时所采取的步骤似乎与我的链接中的步骤几乎相同,但我仍然无法弄清楚每个方法在被调用时的作用,例如在反射下。应用列出的步骤 4. 执行 PrepareForTailCall 抽象操作。 5.返回调用target的[[Call]]内部方法的结果,带参数thisArgument和args--------这是什么意思?
  • 我最好的猜测是它在第 4 步中引用了尾递归,第 5 步是对原型函数的引用。看起来一般的想法是验证 apply 方法可以在其应用的对象上运行(步骤 1-2)、错误句柄(步骤 3),然后调用正在运行的函数 apply (步骤 4-5)。通过浏览文档,我的最佳猜测是 Reflect 模块的重点是当您执行需要某种形式的对象自省的功能时。 call 的使用也可能是它“没有 [[Call]] 内部方法”的原因。
猜你喜欢
  • 2018-06-29
  • 1970-01-01
  • 1970-01-01
  • 2011-03-06
  • 1970-01-01
  • 2017-09-09
相关资源
最近更新 更多