Cosa fa l'oggetto Reflect in JavaScript?


91

Ho visto uno stub vuoto su MDN qualche tempo fa per l' Reflectoggetto in javascript ma non riesco a trovare nulla su Google. Oggi ho trovato questo http://people.mozilla.org/~jorendorff/es6-draft.html#sec-reflect-object e suona simile all'oggetto Proxy a parte le funzionalità realm e loader.

Fondamentalmente, non so se questa pagina che ho trovato spieghi solo come implementare Reflect o se non riesco a capirne il testo. Qualcuno potrebbe spiegarmi in generale quali sono i metodi di Reflectfare?

Ad esempio, nella pagina che ho trovato dice che la chiamata Reflect.apply ( target, thisArgument, argumentsList ) "Restituirà il risultato della chiamata al metodo interno [[Call]] di target con argomenti thisArgument e args." ma come è diverso dal semplice chiamare target.apply(thisArgument, argumentsList)?

Aggiornare:

Grazie a @Blue, ho trovato questa pagina sul wiki http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api&s=reflect che, al meglio delle mie conoscenze, dice che l'oggetto riflettere fornisce le versioni del metodo di tutti le azioni che possono essere intrappolate dai proxy per facilitare l'inoltro. Ma questo mi sembra un po 'strano poiché non vedo come sia del tutto necessario. Ma sembra fare un po 'di più, in particolare il par che dice double-liftingma che punta alla vecchia specifica proxy /


1
La specifica dice "L'oggetto Reflect è un singolo oggetto ordinario.", A mio avviso Reflectè solo un contenitore per oggetti Realme Loader, ma non so nemmeno cosa facciano questi ultimi.
simonzack

Grazie :), mi sembra dalla pagina a cui sono collegato (non so quanto sia legittimo) che ogni Reame sia il suo "contesto di java script" e un caricatore carica Reami come moduli o qualcosa del genere, in base alle somiglianze tra riflettere e proxy e il fatto che sorta di proxy di "sovraccarichi" costruito nel funzionalità potrebbe Reflect.Loadere Reflect.Realmavere qualcosa a che con sovraccarico funzionalità del modulo?
Jim Jones

1
Sembra che sia una 'classe statica' (come JSON) con metodi statici: isExtensible, ownKeysecc ES 6, con le classi attuali, questo è utile per scoprire di più su una classe ( targetin 16.1.2 credo).
Rudie

Risposte:


131

UPDATE 2015: Come sottolineato da 7 's risposta , ora che ES6 (ECMAScript 2015) è stato finalizzato, documentazione più appropriata è ora disponibile:


Risposta originale (per comprensione (storica) ed esempi extra) :

L' Reflection proposalsembra essere progredita al Progetto ECMAScript 6 Specification . Questo documento attualmente delinea ilReflect metodi di -object e afferma solo quanto segue Reflectsull'oggetto stesso:

L'oggetto Reflect è un singolo oggetto ordinario.

Il valore dello slot interno [[Prototype]] dell'oggetto Reflect è l'oggetto prototipo Object incorporato standard (19.1.3).

L'oggetto Reflect non è un oggetto funzione. Non ha un metodo interno [[Construct]]; non è possibile utilizzare l'oggetto Reflect come costruttore con l'estensione nuovo operatore . L'oggetto Reflect non dispone inoltre di un metodo interno [[Call]]; non è possibile richiamare l'oggetto Reflect come funzione.

Tuttavia, c'è una breve spiegazione sul suo scopo in ES Harmony :

Il modulo "@reflect" ha molteplici scopi:
  • Ora che abbiamo i moduli, un modulo "@reflect" è un luogo più naturale per molti dei metodi di riflessione precedentemente definiti in Object. Per motivi di compatibilità con le versioni precedenti, è improbabile che i metodi statici su Object scompaiano. Tuttavia, è probabile che nuovi metodi vengano aggiunti al modulo "@reflect" piuttosto che al costruttore Object.
  • Una casa naturale per i proxy, che evita la necessità di un'associazione proxy globale.
  • La maggior parte dei metodi in questo modulo mappa uno a uno su trap proxy. I gestori proxy necessitano di questi metodi per inoltrare comodamente le operazioni, come mostrato di seguito.



Quindi, l' Reflectoggetto fornisce una serie di funzioni di utilità, molte delle quali sembrano sovrapporsi ai metodi ES5 definiti sull'Oggetto globale.

Tuttavia, ciò non spiega realmente quali problemi esistenti si intende risolvere o quali funzionalità vengono aggiunte. Sospettavo che questo potesse essere attenuato e, in effetti, le specifiche dell'armonia di cui sopra si collegano a una "implementazione non normativa e approssimativa di questi metodi" .

L'esame di quel codice potrebbe dare (ulteriori) idee sul suo utilizzo, ma per fortuna c'è anche un wiki che delinea una serie di ragioni per cui l'oggetto Reflect è utile :
(Ho copiato (e formattato) il testo seguente per riferimento futuro da quello fonte in quanto sono gli unici esempi che sono riuscito a trovare. Inoltre, hanno senso, hanno già una buona spiegazione e toccano l' applyesempio della domanda .)


Valori di ritorno più utili

Molte operazioni in Reflectsono simili alle operazioni ES5 definite su Object, come Reflect.getOwnPropertyDescriptore Reflect.defineProperty. Tuttavia, mentre Object.defineProperty(obj, name, desc)verrà restituito objquando la proprietà è stata definita correttamente o verrà generato un TypeErroraltro, Reflect.defineProperty(obj, name, desc)è specificato che restituisca semplicemente un valore booleano che indica se la proprietà è stata definita correttamente. Ciò ti consente di eseguire il refactoring di questo codice:

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

A questa:

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

Altri metodi che restituiscono un tale stato di successo booleano sono Reflect.set(per aggiornare una proprietà), Reflect.deleteProperty(per eliminare una proprietà), Reflect.preventExtensions(per rendere un oggetto non estensibile) e Reflect.setPrototypeOf(per aggiornare il collegamento prototipo di un oggetto).


Operazioni di prima classe

In ES5, il modo per rilevare se un oggetto objdefinisce o eredita un determinato nome di proprietà è scrivere (name in obj). Allo stesso modo, per eliminare una proprietà, si usa delete obj[name]. Sebbene la sintassi dedicata sia carina e breve, significa anche che devi racchiudere esplicitamente queste operazioni in funzioni quando vuoi passare l'operazione come valore di prima classe.

Con Reflect, queste operazioni sono prontamente definite come funzioni di prima classe:
Reflect.has(obj, name)è l'equivalente funzionale di (name in obj)ed Reflect.deleteProperty(obj, name)è una funzione che fa lo stesso didelete obj[name].


Applicazione di funzioni più affidabile

In ES5, quando si vuole chiamare una funzione fcon un numero variabile di argomenti impacchettati come un array argse vincolando il thisvalore a obj, si può scrivere:

f.apply(obj, args)

Tuttavia, fpotrebbe essere un oggetto che intenzionalmente o non intenzionalmente definisce il proprio applymetodo. Quando vuoi davvero assicurarti che la applyfunzione incorporata sia chiamata, in genere si scrive:

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

Non solo questo è prolisso, ma diventa rapidamente difficile da capire. Con Reflect, ora puoi effettuare una chiamata di funzione affidabile in un modo più breve e più facile da capire:

Reflect.apply(f, obj, args)


Costruttori di argomenti variabili

Immagina di voler chiamare una funzione di costruzione con un numero variabile di argomenti. In ES6, grazie alla nuova sintassi spread, sarà possibile scrivere codice come:

var obj = new F(...args)

In ES5, questo è più difficile da scrivere, perché si può solo usare F.applyo F.callchiamare una funzione con un numero variabile di argomenti, ma non c'è nessuna F.constructfunzione per newla funzione con un numero variabile di argomenti. Con Reflect, ora si può scrivere, in ES5:

var obj = Reflect.construct(F, args)


Comportamento di inoltro predefinito per trap proxy

Quando si usano Proxyoggetti per racchiudere oggetti esistenti, è molto comune intercettare un'operazione, fare qualcosa e poi "fare la cosa predefinita", che è tipicamente applicare l'operazione intercettata all'oggetto avvolto. Ad esempio, diciamo che voglio semplicemente registrare tutti gli accessi alle proprietà di un oggetto obj:

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

Le API Reflecte sono state progettate in tandem , in modo tale che per ogni trap esiste un metodo corrispondente che "fa la cosa predefinita". Quindi, ogni volta che ti ritrovi a voler "fare la cosa predefinita" all'interno di un gestore Proxy, la cosa corretta da fare è chiamare sempre il metodo corrispondente nell'oggetto:ProxyProxyReflectReflect

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

ReflectÈ garantito che il tipo restituito dei metodi sia compatibile con il tipo restituito dei Proxytrap.


Controlla questo collegamento delle funzioni di accesso

In ES5 è abbastanza facile eseguire un accesso generico alla proprietà o un aggiornamento della proprietà. Per esempio:

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

I metodi Reflect.gete Reflect.setti consentono di fare la stessa cosa, ma in aggiunta accetta come ultimo argomento opzionale un receiverparametro che ti consente di impostare esplicitamente il this-binding quando la proprietà che ottieni / imposta è una funzione di accesso:

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)

Questo è occasionalmente utile quando si esegue il wrapping obje si desidera che qualsiasi autoinvio all'interno dell'accessor objvenga reindirizzato al wrapper, ad esempio if è definito come:

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

La chiamata Reflect.get(obj, "foo", wrapper)causerà il this.bar()reindirizzamento della chiamata a wrapper.


Evita l'eredità __proto__

In alcuni browser, __proto__è definita come una proprietà speciale che dà accesso al prototipo di un oggetto. ES5 ha standardizzato un nuovo metodo Object.getPrototypeOf(obj)per interrogare il prototipo. Reflect.getPrototypeOf(obj)fa esattamente lo stesso, tranne che Reflectdefinisce anche un corrispondente Reflect.setPrototypeOf(obj, newProto)per impostare il prototipo dell'oggetto. Questo è il nuovo modo conforme a ES6 per aggiornare il prototipo di un oggetto.
Si noti che: setPrototypeOf anche esisteObject (come giustamente sottolineato da Knu 's commenti )!


EDIT:
Nota a margine (indirizzando i commenti alla domanda ): c'è una risposta breve e semplice su "Q: moduli ES6 e importazioni HTML" che spiega Realmse Loaderoggetti.

Un'altra spiegazione è offerta da questo link :

Un oggetto realm astrae la nozione di un ambiente globale distinto, con il proprio oggetto globale, copia della libreria standard e "intrinseci" (oggetti standard che non sono vincolati a variabili globali, come il valore iniziale di Object.prototype).

Web estensibile : questo è l'equivalente dinamico di una stessa origine <iframe>senza DOM.

Da segnalare però: tutto questo è ancora in bozza, non si tratta di una specifica incisa nella pietra! È ES6, quindi tieni presente la compatibilità del browser!

Spero che sia di aiuto!


@Spencer Killen: Beh .. questa risposta ha indirizzato i tuoi pensieri nella giusta direzione e ha spiegato come questo si collega alla differenza tra Reflect.applye target.apply? O cosa dovrei aggiungere prima che finisca la taglia?
GitaarLAB

2
setPrototypeOf esiste anche su Object.
Knu

1
Si noti che l'utilizzo Reflect.getcome implementazione predefinita per proxy get non funziona bene se si esegue il proxy di un oggetto con proprietà prototipo. Si lamenta solo che non funziona. Tuttavia, se invece usi Reflect.get(target, property)senza passare il receiver, allora funziona.
CMCDragonkai

I miei test in cui si accede sempre alle proprietà tramite il proxy, si traduce in una situazione in cui il targetè l'obiettivo originale che il proxy avvolge, mentre receiverè il proxy stesso. Ma ancora una volta questo potrebbe essere diverso se riesci ad accedere alle proprietà in modo diverso.
CMCDragonkai

Questa è una risposta fantastica! Grazie
rpivovar

5

Seguendo la bozza di documento trovata sul wiki,

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

Otteniamo la linea sul "singolo oggetto ordinario" che chiarisce nella bozza. Ha anche le definizioni delle funzioni.

Il wiki dovrebbe essere affidabile poiché puoi trovare un collegamento ad esso dal sito web di emcascript

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

Tuttavia ho trovato il primo collegamento di Google e non ho avuto fortuna a trovarlo cercando direttamente nel wiki.


Grazie, i passaggi effettuati quando ogni metodo viene chiamato elencato nelle specifiche ufficiali sembrano più o meno gli stessi di quelli nel mio link, ma non riesco ancora a capire cosa fa ogni metodo quando viene chiamato, ad esempio in Reflect. Applicare i passaggi elencati 4. Eseguire l'operazione astratta PrepareForTailCall. 5. Restituisce il risultato della chiamata al metodo interno [[Call]] di target con argomenti thisArgument e args ------- cosa significa?
Jim Jones,

La mia ipotesi migliore da guardarla è che fa riferimento alla ricorsione della coda nel passaggio 4 e il passaggio 5 è un riferimento alla funzione prototipo. Sembra che l'idea generale sia quella di verificare che il metodo apply possa essere eseguito su ciò a cui è applicato (passaggi 1-2), handle di errore (passaggio 3) e quindi richiamare la funzione su cui viene eseguita l'applicazione (passaggi 4-5). La mia ipotesi migliore dalla scansione attraverso la documentazione è che il punto del modulo Reflect è quando si esegue la funzionalità che richiede una qualche forma di introspezione dell'oggetto. L'uso di call è probabilmente anche il motivo per cui "non ha un metodo interno [[Call]]".
Blu
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.