¿Qué hace el objeto Reflect en JavaScript?

87

Vi un código auxiliar en blanco en MDN hace un tiempo para el Reflectobjeto en javascript, pero no puedo por mi vida encontrar nada en Google. Hoy encontré este http://people.mozilla.org/~jorendorff/es6-draft.html#sec-reflect-object y suena similar al objeto Proxy, aparte de la funcionalidad de reino y cargador.

Básicamente, no sé si esta página que encontré solo explica cómo implementar Reflect o si simplemente no puedo entender su redacción. ¿Podría alguien explicarme en general cuáles son los métodos Reflect?

Por ejemplo, en la página que encontré dice que la llamada Reflect.apply ( target, thisArgument, argumentsList ) "Devolverá el resultado de llamar al método interno [[Call]] del objetivo con argumentos thisArgument y args". pero, ¿en qué se diferencia eso de simplemente llamar target.apply(thisArgument, argumentsList)?

Actualizar:

Gracias a @Blue, encontré esta página en la wiki http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api&s=reflect que, a mi leal saber y entender, dice que el objeto reflect proporciona versiones de métodos de todos las acciones que pueden ser atrapadas por los proxies para facilitar el reenvío. Pero eso me parece un poco extraño ya que no veo cómo es del todo necesario. Pero parece hacer un poco más que eso, particularmente el par que dice double-liftingpero que apunta a la antigua especificación de proxy /

Jim Jones
fuente
1
La especificación dice "El objeto Reflect es un solo objeto ordinario", a mi entender Reflectes solo un contenedor para objetos Realmy Loader, pero tampoco sé qué hacen estos últimos.
Simonzack
Gracias :), por la página a la que he vinculado (no sé qué tan legítimo es) parece que cada Realm es su propio "contexto de script java" y un cargador carga Realms como módulos o algo así, según las similitudes entre reflect y el proxy y el hecho de que el tipo de proxy de "sobrecarga" construido en funcionalidad podría Reflect.Loadery Reflect.Realmtener algo que ver con la sobrecarga de la funcionalidad del módulo?
Jim Jones
1
Parece que es una 'clase estática' (como JSON) con métodos estáticos: isExtensible, ownKeysetc. ES 6, con clases reales, esto es útil para saber más acerca de una clase ( targeten 16.1.2 creo).
Rudie

Respuestas:

129

ACTUALIZACIÓN 2015: Como se señaló en la respuesta de 7th , ahora que ES6 (ECMAScript 2015) se ha finalizado, ahora se encuentra disponible documentación más apropiada:


Respuesta original (para comprensión (histórica) y ejemplos adicionales) :

El Reflection proposalparece haber progresado hasta el Proyecto de ECMAScript 6 Especificación . Este documento actualmente describe los Reflectmétodos del -objeto y solo establece lo siguiente sobre el Reflect-objeto en sí:

El objeto Reflect es un solo objeto ordinario.

El valor de la ranura interna [[Prototype]] del objeto Reflect es el objeto prototipo de objeto integrado estándar (19.1.3).

El objeto Reflect no es un objeto de función. No tiene un método interno [[Construct]]; no es posible utilizar el objeto Reflect como constructor con el operador new . El objeto Reflect tampoco tiene un método interno [[Call]]; no es posible invocar el objeto Reflect como función.

Sin embargo, hay una breve explicación sobre su propósito en ES Harmony :

El módulo "@reflect" tiene múltiples propósitos:
  • Ahora que tenemos módulos, un módulo “@reflect” es un lugar más natural para muchos de los métodos de reflexión previamente definidos en Object. Por motivos de compatibilidad con versiones anteriores, es poco probable que desaparezcan los métodos estáticos de Object. Sin embargo, es probable que se agreguen nuevos métodos al módulo “@reflect” en lugar de al constructor de objetos.
  • Un hogar natural para los proxies, evitando la necesidad de un enlace Proxy global.
  • La mayoría de los métodos de este módulo se asignan uno a uno a las trampas de proxy. Los controladores de proxy necesitan estos métodos para reenviar operaciones de manera conveniente, como se muestra a continuación.



Por lo tanto, el Reflectobjeto proporciona una serie de funciones de utilidad, muchas de las cuales parecen superponerse con los métodos de ES5 definidos en el objeto global.

Sin embargo, eso realmente no explica qué problemas existentes pretende resolver o qué funcionalidad se agrega. Sospeché que esto podría ser alterado y, de hecho, la especificación de armonía anterior se vincula a una `` implementación aproximada no normativa de estos métodos '' .

Examinar ese código podría dar (más) ideas sobre su uso, pero afortunadamente también hay una wiki que describe una serie de razones por las que el objeto Reflect es útil :
(He copiado (y formateado) el siguiente texto para referencia futura de ese fuente ya que son los únicos ejemplos que pude encontrar. Además de eso, tienen sentido, ya tienen una buena explicación y tocan el applyejemplo de la pregunta .)


Valores de retorno más útiles

Muchas operaciones en Reflectson similares a las operaciones de ES5 definidas en Object, como Reflect.getOwnPropertyDescriptory Reflect.defineProperty. Sin embargo, mientras Object.defineProperty(obj, name, desc)que retornará objcuando la propiedad se definió con éxito, o arrojará un valor diferente TypeError, Reflect.defineProperty(obj, name, desc)se especifica para devolver simplemente un valor booleano que indique si la propiedad se definió correctamente o no. Esto le permite refactorizar este código:

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

A esto:

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

Otros métodos que devuelven un estado de éxito booleano son Reflect.set(actualizar una propiedad), Reflect.deleteProperty(eliminar una propiedad), Reflect.preventExtensions(hacer que un objeto no sea extensible) y Reflect.setPrototypeOf(actualizar el enlace del prototipo de un objeto).


Operaciones de primera

En ES5, la forma de detectar si un objeto objdefine o hereda un determinado nombre de propiedad es escribir (name in obj). De manera similar, para eliminar una propiedad, se usa delete obj[name]. Si bien la sintaxis dedicada es agradable y breve, también significa que debe envolver explícitamente estas operaciones en funciones cuando desee pasar la operación como un valor de primera clase.

Con Reflect, estas operaciones se definen fácilmente como funciones de primera clase:
Reflect.has(obj, name)es el equivalente funcional de (name in obj)y Reflect.deleteProperty(obj, name)es una función que hace lo mismo quedelete obj[name].


Aplicación de funciones más confiable

En ES5, cuando uno quiere llamar a una función fcon un número variable de argumentos empaquetados como una matriz argsy enlazando el thisvalor obj, se puede escribir:

f.apply(obj, args)

Sin embargo, fpodría ser un objeto que, de forma intencionada o no, defina su propio applymétodo. Cuando realmente quiere asegurarse de que applyse llama a la función incorporada , normalmente se escribe:

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

Esto no solo es detallado, sino que rápidamente se vuelve difícil de entender. Con Reflect, ahora puede realizar una llamada de función confiable de una manera más corta y más fácil de entender:

Reflect.apply(f, obj, args)


Constructores de argumentos variables

Imagina que quieres llamar a una función constructora con un número variable de argumentos. En ES6, gracias a la nueva sintaxis de propagación, será posible escribir código como:

var obj = new F(...args)

En ES5, esto es más difícil de escribir, porque solo se puede usar F.applyo F.callllamar a una función con un número variable de argumentos, pero no hay F.constructfunción para newla función con un número variable de argumentos. Con Reflect, ahora se puede escribir, en ES5:

var obj = Reflect.construct(F, args)


Comportamiento de reenvío predeterminado para capturas de proxy

Cuando se utilizan Proxyobjetos para envolver objetos existentes, es muy común interceptar una operación, hacer algo y luego "hacer lo predeterminado", que normalmente es aplicar la operación interceptada al objeto envuelto. Por ejemplo, digamos que simplemente quiero registrar todos los accesos de propiedad a un objeto obj:

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

Las API Reflecty se diseñaron en conjunto , de modo que para cada trampa, existe un método correspondiente que "hace lo predeterminado". Por lo tanto, cada vez que desee "hacer lo predeterminado" dentro de un controlador Proxy, lo correcto es llamar siempre al método correspondiente en el objeto:ProxyProxyReflectReflect

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

Se Reflectgarantiza que el tipo de retorno de los métodos es compatible con el tipo de retorno de las Proxytrampas.


Controlar la vinculación de los accesos

En ES5, es bastante fácil realizar un acceso de propiedad genérico o una actualización de propiedad. Por ejemplo:

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

Los métodos Reflect.gety le Reflect.setpermiten hacer lo mismo, pero además aceptan como último argumento opcional un receiverparámetro que le permite establecer explícitamente el this-binding cuando la propiedad que obtiene / establece es un descriptor de acceso:

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)

En ocasiones, esto es útil cuando está ajustando objy desea que cualquier autoenvío dentro del descriptor de acceso sea redirigido a su envoltorio, por ejemplo, si objse define como:

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

Llamar Reflect.get(obj, "foo", wrapper)hará que la this.bar()llamada se desvíe a wrapper.


Evita el legado __proto__

En algunos navegadores, __proto__se define como una propiedad especial que da acceso al prototipo de un objeto. ES5 estandarizó un nuevo método Object.getPrototypeOf(obj)para consultar el prototipo. Reflect.getPrototypeOf(obj)hace exactamente lo mismo, excepto que Reflecttambién define un correspondiente Reflect.setPrototypeOf(obj, newProto)para configurar el prototipo del objeto. Esta es la nueva forma compatible con ES6 de actualizar el prototipo de un objeto.
Tenga en cuenta que: setPrototypeOf también existe enObject (como se indica correctamente en el comentario de Knu ).


EDITAR:
Nota al margen (dirigiendo comentarios a la Q): Hay una respuesta corta y simple en 'Q: Módulos ES6 vs. Importaciones HTML' que explica Realmsy Loaderobjetos.

Este enlace ofrece otra explicación :

Un objeto de reino abstrae la noción de un entorno global distinto, con su propio objeto global, copia de la biblioteca estándar e "intrínsecos" (objetos estándar que no están vinculados a variables globales, como el valor inicial de Object.prototype).

Web extensible : este es el equivalente dinámico de un mismo origen <iframe>sin DOM.

Sin embargo, vale la pena mencionarlo: todo esto aún está en borrador, ¡esta no es una especificación grabada en piedra! Es ES6, ¡así que tenga en cuenta la compatibilidad del navegador!

¡Espero que esto ayude!

GitaarLAB
fuente
@Spencer Killen: Bueno ... ¿esta respuesta apuntó sus pensamientos en la dirección correcta y explicó cómo esto se relaciona con la diferencia entre Reflect.applyy target.apply? ¿O qué debo agregar antes de que termine la recompensa?
GitaarLAB
2
setPrototypeOf también existe en Object.
Knu
1
Tenga en cuenta que usar Reflect.getcomo implementación predeterminada para proxy get no funciona bien si está usando un objeto con propiedades de prototipo. Simplemente se queja de que no funciona. Sin embargo, si usa Reflect.get(target, property)sin pasar el receiver, entonces funciona.
CMCDragonkai
Mis pruebas en las que siempre se accede a las propiedades a través del proxy, dan como resultado una situación en la que el targetes el objetivo original que envuelve el proxy, mientras que receiveres el propio proxy. Pero nuevamente, esto podría ser diferente si logra acceder a las propiedades de manera diferente.
CMCDragonkai
5

Siguiendo el borrador del documento que se encuentra en la wiki,

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

Obtenemos la línea sobre "objeto ordinario único" que aclara en el borrador. También tiene las definiciones de funciones.

La wiki debe ser confiable, ya que puede encontrar un enlace desde el sitio web de emcascript.

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

Sin embargo, encontré el primer enlace de Google y no tuve suerte para encontrarlo buscando directamente en la wiki.

Azul
fuente
Gracias, los pasos que se toman cuando se llama a cada método enumerado en la especificación oficial parecen más o menos los mismos que los de mi enlace, pero todavía no puedo averiguar qué hace cada método cuando se llama, por ejemplo, en Reflect. Aplicar los pasos que se enumeran 4. Realice la operación abstracta PrepareForTailCall. 5. Devuelve el resultado de llamar al método interno [[Call]] de target con argumentos thisArgument y args ------- ¿qué significa eso?
Jim Jones
Mi mejor suposición al mirarlo es que hace referencia a la recursividad de cola en el paso 4 y el paso 5 es una referencia a la función de prototipo. Parece que la idea general es verificar que el método de aplicación se puede ejecutar en lo que se aplica (pasos 1-2), manejar el error (paso 3) y luego invocar la función que se está ejecutando aplicar (pasos 4-5). Mi mejor suposición al escanear la documentación es que el punto del módulo Reflect es cuando está haciendo una funcionalidad que requiere alguna forma de introspección del objeto. El uso de call también es probablemente la razón por la que "no tiene un método interno [[Call]]".
Azul