Que fait l'objet Reflect en JavaScript?

87

J'ai vu un talon vierge sur MDN il y a quelque temps pour l' Reflectobjet en javascript, mais je ne peux rien trouver sur Google pendant toute ma vie. Aujourd'hui, j'ai trouvé cet http://people.mozilla.org/~jorendorff/es6-draft.html#sec-reflect-object et cela ressemble à l'objet Proxy en dehors du domaine et des fonctionnalités du chargeur.

Fondamentalement, je ne sais pas si cette page que j'ai trouvée explique uniquement comment implémenter Reflect ou si je ne comprends tout simplement pas sa formulation. Quelqu'un pourrait-il s'il vous plaît m'expliquer généralement ce que les méthodes de Reflectfaire?

Par exemple, sur la page que j'ai trouvée, il est dit que l'appel Reflect.apply ( target, thisArgument, argumentsList ) "retournera le résultat de l'appel de la méthode interne [[Call]] de la cible avec les arguments thisArgument et args." mais en quoi est-ce différent de simplement appeler target.apply(thisArgument, argumentsList)?

Mise à jour:

Grâce à @Blue, j'ai trouvé cette page sur le wiki http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api&s=reflect qui, à ma connaissance, indique que l'objet Reflect fournit des versions de méthode de tous les actions qui peuvent être piégées par des proxys pour faciliter le transfert. Mais cela me semble un peu bizarre car je ne vois pas en quoi c'est entièrement nécessaire. Mais il semble faire un peu plus que cela, en particulier le pair qui dit double-liftingmais qui pointe vers l'ancienne spécification du proxy /

Jim Jones
la source
1
La spécification dit "L'objet Reflect est un objet ordinaire unique.", À ma connaissance Reflectn'est qu'un conteneur pour Realmet des Loaderobjets, mais je ne sais pas non plus ce que font ces derniers.
simonzack
Merci :), il me semble d'après la page à laquelle j'ai lié (je ne sais pas à quel point c'est légitime) que chaque royaume est son propre "contexte de script java" et qu'un chargeur charge des royaumes comme des modules ou quelque chose du genre, en fonction des similitudes entre refléter et le proxy et le fait que le type de proxy de la fonctionnalité intégrée de «surcharges» pourrait Reflect.Loaderet Reflect.Realmavoir quelque chose à avec la fonctionnalité de module de surcharge?
Jim Jones
1
On dirait que c'est une « classe statique » avec des méthodes de statiques (comme JSON): isExtensible, ownKeysetc. ES 6, avec des classes réelles, ce qui est utile pour en savoir plus sur une classe ( targeten 16.1.2 , je pense).
Rudie

Réponses:

129

MISE À JOUR 2015: Comme indiqué par la réponse de 7th , maintenant que ES6 (ECMAScript 2015) est finalisé, une documentation plus appropriée est désormais disponible:


Réponse originale (pour une compréhension (historique) et des exemples supplémentaires) :

Le Reflection proposalsemble avoir progressé vers le projet de spécification ECMAScript 6 . Ce document décrit actuellement les Reflectméthodes de -object et indique uniquement ce qui suit sur Reflect-object lui-même:

L'objet Reflect est un objet ordinaire unique.

La valeur de l'emplacement interne [[Prototype]] de l'objet Reflect est l'objet prototype Object intégré standard (19.1.3).

L'objet Reflect n'est pas un objet de fonction. Il n'a pas de méthode interne [[Construct]]; il n'est pas possible d'utiliser l'objet Reflect comme constructeur avec l' opérateur new . L'objet Reflect n'a pas non plus de méthode interne [[Call]]; il n'est pas possible d'invoquer l'objet Reflect en tant que fonction.

Cependant, il y a une brève explication sur son objectif dans ES Harmony :

Le module «@reflect» a plusieurs objectifs:
  • Maintenant que nous avons des modules, un module «@reflect» est un endroit plus naturel pour de nombreuses méthodes de réflexion précédemment définies sur Object. À des fins de compatibilité descendante, il est peu probable que les méthodes statiques sur Object disparaissent. Cependant, de nouvelles méthodes devraient probablement être ajoutées au module «@reflect» plutôt qu'au constructeur Object.
  • Une maison naturelle pour les proxies, évitant le besoin d'une liaison Proxy globale.
  • La plupart des méthodes de ce module mappent un à un sur les interruptions Proxy. Les gestionnaires de proxy ont besoin de ces méthodes pour transférer facilement les opérations, comme indiqué ci-dessous.



Ainsi, l' Reflectobjet fournit un certain nombre de fonctions utilitaires, dont beaucoup semblent chevaucher les méthodes ES5 définies sur l'objet global.

Cependant, cela n'explique pas vraiment quels problèmes existants cela a l'intention de résoudre ou quelles fonctionnalités sont ajoutées. Je soupçonnais que cela pouvait être corrigé et en effet, les spécifications d'harmonie ci-dessus sont liées à une «implémentation approximative et non normative de ces méthodes» .

L'examen de ce code pourrait donner une (plus) idée de son utilisation, mais heureusement, il existe également un wiki qui décrit un certain nombre de raisons pour lesquelles l'objet Reflect est utile :
(J'ai copié (et formaté) le texte suivant pour référence future à partir de cela source car ce sont les seuls exemples que j'ai pu trouver. En plus de cela, ils ont du sens, ont déjà une bonne explication et touchent l' applyexemple de la question .)


Valeurs de retour plus utiles

De nombreuses opérations dans Reflectsont similaires aux opérations ES5 définies sur Object, telles que Reflect.getOwnPropertyDescriptoret Reflect.defineProperty. Cependant, alors que Object.defineProperty(obj, name, desc)sera soit retourné objlorsque la propriété a été définie avec succès, soit jeté un TypeErrorsinon, Reflect.defineProperty(obj, name, desc)est spécifié pour simplement renvoyer un booléen qui indique si la propriété a été définie avec succès ou non. Cela vous permet de refactoriser ce code:

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

Pour ça:

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

Les autres méthodes qui renvoient un tel état de réussite booléen sont Reflect.set(pour mettre à jour une propriété), Reflect.deleteProperty(pour supprimer une propriété), Reflect.preventExtensions(pour rendre un objet non extensible) et Reflect.setPrototypeOf(pour mettre à jour le lien prototype d'un objet).


Opérations de premier ordre

Dans ES5, le moyen de détecter si un objet objdéfinit ou hérite d'un certain nom de propriété est d'écrire (name in obj). De même, pour supprimer une propriété, on utilise delete obj[name]. Bien que la syntaxe dédiée soit agréable et courte, cela signifie également que vous devez explicitement encapsuler ces opérations dans des fonctions lorsque vous souhaitez transmettre l'opération en tant que valeur de première classe.

Avec Reflect, ces opérations sont facilement définies comme des fonctions de première classe:
Reflect.has(obj, name)est l'équivalent fonctionnel de (name in obj)et Reflect.deleteProperty(obj, name)est une fonction qui fait la même chose quedelete obj[name].


Application de fonction plus fiable

Dans ES5, quand on veut appeler une fonction favec un nombre variable d'arguments emballés sous forme de tableau argset lier la thisvaleur à obj, on peut écrire:

f.apply(obj, args)

Cependant, fpourrait être un objet qui définit intentionnellement ou non sa propre applyméthode. Lorsque vous voulez vraiment vous assurer que la applyfonction intégrée est appelée, on écrit généralement:

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

Non seulement cela est verbeux, mais cela devient rapidement difficile à comprendre. Avec Reflect, vous pouvez désormais effectuer un appel de fonction fiable de manière plus courte et plus simple à comprendre:

Reflect.apply(f, obj, args)


Constructeurs d'arguments variables

Imaginez que vous vouliez appeler une fonction constructeur avec un nombre variable d'arguments. Dans ES6, grâce à la nouvelle syntaxe de diffusion, il sera possible d'écrire du code comme:

var obj = new F(...args)

Dans ES5, c'est plus difficile à écrire, car on ne peut utiliser F.applyou F.callappeler qu'une fonction avec un nombre variable d'arguments, mais il n'y a pas de F.constructfonction à newla fonction avec un nombre variable d'arguments. Avec Reflect, on peut maintenant écrire, dans ES5:

var obj = Reflect.construct(F, args)


Comportement de transfert par défaut pour les interruptions proxy

Lorsque vous utilisez des Proxyobjets pour envelopper des objets existants, il est très courant d'intercepter une opération, de faire quelque chose, puis de «faire la chose par défaut», ce qui consiste généralement à appliquer l'opération interceptée à l'objet enveloppé. Par exemple, disons que je veux simplement enregistrer tous les accès aux propriétés d'un objet obj:

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

Les API Reflectet ont été conçues en tandem , de sorte que pour chaque trap, il existe une méthode correspondante sur laquelle "fait la chose par défaut". Par conséquent, chaque fois que vous avez envie de "faire la chose par défaut" dans un gestionnaire de proxy, la bonne chose à faire est de toujours appeler la méthode correspondante dans l' objet:ProxyProxyReflectReflect

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

Le type de retour des Reflectméthodes est garanti compatible avec le type de retour des Proxytraps.


Contrôler la liaison des accesseurs

Dans ES5, il est assez facile de faire un accès à une propriété générique ou une mise à jour de propriété. Par exemple:

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

Les méthodes Reflect.getet Reflect.setvous permettent de faire la même chose, mais acceptent en plus comme dernier argument facultatif un receiverparamètre qui vous permet de définir explicitement la thisliaison lorsque la propriété que vous obtenez / définissez est un accesseur:

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)

Ceci est parfois utile lorsque vous encapsulez objet que vous voulez que tous les auto-envois dans l'accesseur soient redirigés vers votre wrapper, par exemple si objest défini comme:

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

L'appel Reflect.get(obj, "foo", wrapper)entraînera le this.bar()réacheminement de l' appel vers wrapper.


Évitez l'héritage __proto__

Sur certains navigateurs, __proto__est défini comme une propriété spéciale qui donne accès au prototype d'un objet. ES5 a standardisé une nouvelle méthode Object.getPrototypeOf(obj)pour interroger le prototype. Reflect.getPrototypeOf(obj)fait exactement la même chose, sauf que Reflectdéfinit également un correspondant Reflect.setPrototypeOf(obj, newProto)pour définir le prototype de l'objet. Il s'agit du nouveau moyen conforme à ES6 de mettre à jour le prototype d'un objet.
Notez que: existe setPrototypeOf aussi surObject (comme indiqué correctement par le commentaire de Knu )!


EDIT:
Note d' accompagnement (adressant les commentaires au Q): Il y a une réponse courte et simple sur «Q: Modules ES6 vs importations HTML» qui explique Realmset Loaderobjets.

Une autre explication est offerte par ce lien :

Un objet de domaine fait abstraction de la notion d'environnement global distinct, avec son propre objet global, une copie de la bibliothèque standard et des "intrinsèques" (objets standard qui ne sont pas liés à des variables globales, comme la valeur initiale d'Object.prototype).

Web extensible : c'est l'équivalent dynamique d'une même origine <iframe>sans DOM.

A noter cependant: tout cela est encore à l'état de brouillon, ce n'est pas une spécification gravée dans la pierre! C'est ES6, alors gardez à l'esprit la compatibilité du navigateur!

J'espère que cela t'aides!

GitaarLAB
la source
@Spencer Killen: Eh bien ... cette réponse a-t-elle orienté vos pensées dans la bonne direction et expliqué comment cela se rapporte à la différence entre Reflect.applyet target.apply? Ou que dois-je ajouter avant la fin de la prime?
GitaarLAB
2
setPrototypeOf existe également sur Object.
Knu
1
Notez que l'utilisation Reflect.getcomme implémentation par défaut pour le proxy get ne fonctionne pas correctement si vous effectuez un proxy d'un objet avec des propriétés de prototype. Il se plaint simplement que cela ne fonctionne pas. Cependant, si vous utilisez à la place Reflect.get(target, property)sans passer le receiver, cela fonctionne.
CMCDragonkai
Mes tests, où vous accédez toujours aux propriétés via le proxy, aboutissent à une situation dans laquelle targetest la cible d'origine que le proxy enveloppe, tandis que receiverle proxy lui-même. Mais encore une fois, cela pourrait être différent si vous parvenez à accéder aux propriétés différemment.
CMCDragonkai
5

En passant par le projet de document trouvé sur le wiki,

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

Nous obtenons la ligne sur "un seul objet ordinaire" qu'elle clarifie dans le brouillon. Il contient également les définitions de fonction.

Le wiki doit être fiable car vous pouvez trouver un lien vers celui-ci sur le site Web emcascript

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

J'ai trouvé le premier lien par google et je n'ai pas eu de chance de le trouver en cherchant directement sur le wiki.

Bleu
la source
Merci, les étapes suivies lorsque chaque méthode est appelée répertoriée dans la spécification officielle semblent à peu près identiques à celles de mon lien, mais je ne peux toujours pas comprendre ce que fait chaque méthode lorsqu'elle est appelée, par exemple sous Reflect. Appliquer les étapes sont répertoriées 4. Exécutez l'opération abstraite PrepareForTailCall. 5. Renvoie le résultat de l'appel de la méthode interne [[Call]] de la cible avec les arguments thisArgument et args ------- qu'est-ce que cela signifie?
Jim Jones
Ma meilleure supposition en y regardant est qu'il fait référence à la récursivité de queue à l'étape 4 et l'étape 5 est une référence à la fonction prototype. Il semble que l'idée générale est de vérifier que la méthode apply peut s'exécuter sur ce à quoi elle est appliquée (étapes 1 et 2), le gestionnaire d'erreur (étape 3), puis d'appeler la fonction apply est exécutée (étapes 4-5). Ma meilleure hypothèse en parcourant la documentation est que le but du module Reflect est lorsque vous faites une fonctionnalité qui nécessite une forme d'introspection de l'objet. L'utilisation de call est probablement aussi la raison pour laquelle il "n'a pas de méthode interne [[Call]]".
Bleu