Co robi obiekt Reflect w JavaScript?

87

Jakiś czas temu widziałem pusty fragment na MDN dla Reflectobiektu w javascript, ale za całe życie nie mogę znaleźć niczego w Google. Dzisiaj znalazłem ten http://people.mozilla.org/~jorendorff/es6-draft.html#sec-reflect-object i brzmi podobnie do obiektu Proxy, poza dziedziną i funkcjonalnością programu ładującego.

Zasadniczo nie wiem, czy ta strona, którą znalazłem, wyjaśnia tylko, jak zaimplementować Reflect, czy po prostu nie rozumiem jej sformułowania. Czy mógłby mi ktoś ogólnie wyjaśnić jakie metodyReflect to zrobić?

Na przykład na stronie, którą znalazłem, jest napisane, że wywołanie Reflect.apply ( target, thisArgument, argumentsList ) zwróci wynik wywołania wewnętrznej metody [[Call]] celu z argumentami thisArgument i args. ale czym to się różni od zwykłego dzwonienia target.apply(thisArgument, argumentsList)?

Aktualizacja:

Dzięki @Blue znalazłem tę stronę na wiki http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api&s=reflect, która według mojej najlepszej wiedzy mówi, że obiekt Reflect dostarcza wersje metod wszystkich działania, które mogą zostać przechwycone przez serwery proxy, aby ułatwić przekazywanie. Ale wydaje mi się to trochę dziwne, ponieważ nie rozumiem, jak to jest całkowicie konieczne. Ale wydaje się, że robi trochę więcej niż to, szczególnie par, który mówi, double-liftingale wskazuje na starą specyfikację proxy /

Jim Jones
źródło
1
Specyfikacja mówi: „Obiekt Reflect jest pojedynczym zwykłym obiektem”. W moim rozumieniu Reflectjest to tylko pojemnik na Realmi Loaderobiekty, ale nie wiem, co robią te ostatnie.
simonzack
Dzięki :), wydaje mi się ze strony, do której linkowałem (nie wiem, jak to jest uzasadnione), że każda Kraina ma swój własny "kontekst skryptu java", a moduł ładujący ładuje Realms jak moduły lub coś w tym stylu, w oparciu o podobieństwa między reflektorem i proxy oraz fakt, że proxy typu „przeciążanie” wbudowanej funkcjonalności może Reflect.Loaderi Reflect.Realmmieć coś wspólnego z przeciążaniem funkcjonalności modułu?
Jim Jones
1
Wygląda na to, że jest to „klasa statyczna” (jak JSON) z metod statycznych: isExtensible, ownKeysitd. W ES 6, z rzeczywistych klas, to jest użyteczne, aby dowiedzieć się więcej o klasie ( targetw 16.1.2 myślę).
Rudie
Czy widziałeś github.com/tvcutsem/harmony-reflect/wiki ?
kangax

Odpowiedzi:

129

UPDATE 2015: Jak podkreślił 7 „s odpowiedź , teraz ES6 (ECMAScript 2015) została sfinalizowana, bardziej odpowiednia dokumentacja jest już dostępny:


Oryginalna odpowiedź (dla (historycznego) zrozumienia i dodatkowych przykładów) :

Reflection proposalWydaje się, że postęp w projekcie specyfikacji ECMAScript 6 . Ten dokument obecnie przedstawia w Reflectzarysie metody -obiektu i podaje tylko następujące informacje Reflectdotyczące samego -obiektu:

Obiekt Reflect to pojedynczy zwykły obiekt.

Wartość wewnętrznego gniazda [[Prototype]] obiektu Reflect to standardowy wbudowany obiekt prototypowy Object (19.1.3).

Obiekt Reflect nie jest obiektem funkcji. Nie ma wewnętrznej metody [[Construct]]; nie jest możliwe użycie obiektu Reflect jako konstruktora z operatorem new . Obiekt Reflect również nie ma wewnętrznej metody [[Call]]; nie jest możliwe wywołanie obiektu Reflect jako funkcji.

Jednak istnieje krótkie wyjaśnienie jego celu w ES Harmony :

Moduł „@reflect” służy wielu celom:
  • Teraz, gdy mamy moduły, moduł „@reflect” jest bardziej naturalnym miejscem dla wielu metod odbicia zdefiniowanych wcześniej w Object. Ze względu na kompatybilność wsteczną jest mało prawdopodobne, że metody statyczne w Object znikną. Jednak nowe metody powinny być prawdopodobnie dodane do modułu „@reflect” zamiast do konstruktora Object.
  • Naturalne miejsce dla serwerów proxy, eliminujące potrzebę globalnego wiązania proxy.
  • Większość metod w tym module mapuje jeden do jednego na pułapki proxy. Programy obsługi proxy potrzebują tych metod do wygodnego przesyłania dalej operacji, jak pokazano poniżej.



Tak więc Reflect obiekt zapewnia szereg funkcji narzędziowych, z których wiele wydaje się pokrywać z metodami ES5 zdefiniowanymi w globalnym Object.

Jednak to nie wyjaśnia, jakie istniejące problemy ma to rozwiązać, ani jakie funkcje zostały dodane. Podejrzewałem, że można to zniwelować i rzeczywiście, powyższa specyfikacja harmonii łączy się z „nienormatywną, przybliżoną implementacją tych metod” .

Zbadanie tego kodu może dać (dalsze) wyobrażenie o jego użyciu, ale na szczęście istnieje również wiki, które przedstawia szereg powodów, dla których obiekt Reflect jest przydatny :
(Skopiowałem (i sformatowałem) następujący tekst do wykorzystania w przyszłości z tego bo to jedyne przykłady, jakie udało mi się znaleźć. Poza tym mają sens, mają już dobre wyjaśnienie i dotykają applyprzykładu pytania ).


Bardziej przydatne wartości zwracane

Wiele operacji w programie Reflectjest podobnych do operacji ES5 zdefiniowanych w Object, takich jak Reflect.getOwnPropertyDescriptori Reflect.defineProperty. Jednak podczas gdy funkcja Object.defineProperty(obj, name, desc)zwróci, objgdy właściwość została pomyślnie zdefiniowana, lub wyrzuci w TypeErrorprzeciwnym razie, określono Reflect.defineProperty(obj, name, desc)po prostu zwrócenie wartości logicznej, która wskazuje, czy właściwość została pomyślnie zdefiniowana. Pozwala to na refaktoryzację tego kodu:

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

Do tego:

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

Inne metody, które zwracają taki logiczny status sukcesu, to Reflect.set(aby zaktualizować właściwość), Reflect.deleteProperty(aby usunąć właściwość), Reflect.preventExtensions(aby obiekt nie był rozszerzalny) i Reflect.setPrototypeOf(aby zaktualizować łącze prototypu obiektu).


Operacje pierwszej klasy

W ES5 sposobem na wykrycie, czy obiekt objdefiniuje lub dziedziczy określoną nazwę właściwości, jest zapis (name in obj). Podobnie, aby usunąć właściwość, używa się delete obj[name]. Chociaż dedykowana składnia jest ładna i krótka, oznacza to również, że musisz jawnie opakować te operacje w funkcje, jeśli chcesz przekazać operację jako wartość pierwszej klasy.

Dzięki temu Reflectoperacje te można łatwo zdefiniować jako funkcje pierwszej klasy:
Reflect.has(obj, name)jest funkcjonalnym odpowiednikiem (name in obj)i Reflect.deleteProperty(obj, name)jest funkcją, która robi to samo, codelete obj[name].


Bardziej niezawodna aplikacja funkcji

W ES5, gdy chce się wywołać funkcję fze zmienną liczbą argumentów spakowanych jako tablica argsi wiążącą thiswartość do obj, można napisać:

f.apply(obj, args)

Jednak fmoże to być obiekt, który celowo lub nieumyślnie definiuje własną applymetodę. Jeśli naprawdę chcesz się upewnić, że applywywoływana jest funkcja wbudowana , zazwyczaj pisze się:

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

Jest to nie tylko gadatliwe, ale szybko staje się trudne do zrozumienia. Dzięki Reflect, możesz teraz wykonać niezawodne wywołanie funkcji w krótszy i łatwiejszy do zrozumienia sposób:

Reflect.apply(f, obj, args)


Konstruktory ze zmiennymi argumentami

Wyobraź sobie, że chcesz wywołać funkcję konstruktora ze zmienną liczbą argumentów. W ES6, dzięki nowej składni spreadów, będzie można pisać kod taki jak:

var obj = new F(...args)

W ES5 jest to trudniejsze do napisania, ponieważ można używać F.applylub F.callwywoływać tylko funkcję ze zmienną liczbą argumentów, ale nie ma F.constructfunkcji do newfunkcji ze zmienną liczbą argumentów. Dzięki Reflect, można teraz pisać, w ES5:

var obj = Reflect.construct(F, args)


Domyślne zachowanie przekazywania dla pułapek proxy

Podczas używania Proxyobiektów do zawijania istniejących obiektów, bardzo często przechwytuje się operację, robi coś, a następnie „robi coś domyślnego”, co zazwyczaj polega na zastosowaniu przechwyconej operacji na opakowanym obiekcie. Na przykład, powiedzmy, że chcę po prostu rejestrować dostęp do wszystkich właściwości obiektu obj:

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

Funkcje API Reflecti zostały zaprojektowane w tandemie , tak że dla każdej pułapki istnieje odpowiednia metoda, która „robi coś domyślnego”. Dlatego za każdym razem, gdy chcesz wykonać „domyślną” czynność wewnątrz procedury obsługi proxy, należy zawsze wywołać odpowiednią metodę w obiekcie:ProxyProxyReflectReflect

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

Zwracany typ Reflectmetod jest zgodny z typem zwracanym Proxypułapek.


Kontroluj to powiązanie akcesorów

W ES5 dość łatwo jest wykonać ogólny dostęp do właściwości lub aktualizację właściwości. Na przykład:

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

Te Reflect.geti Reflect.setmetody pozwalają zrobić to samo, ale dodatkowo przyjąć jako ostatnia opcjonalnym argumentem jest receiverparametr, który pozwala jawnie ustawić this-binding gdy właściwość, że masz / zestaw jest accessor:

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)

Jest to czasami przydatne, gdy pakujesz obji chcesz, aby wszelkie wysyłki własne w akcesorium zostały przekierowane do twojego opakowania, np. Jeśli objjest zdefiniowane jako:

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

Wywołanie Reflect.get(obj, "foo", wrapper)spowoduje this.bar()przekierowanie połączenia do wrapper.


Unikaj dziedzictwa __proto__

W niektórych przeglądarkach __proto__jest definiowana jako specjalna właściwość, która zapewnia dostęp do prototypu obiektu. ES5 ustandaryzowała nową metodę Object.getPrototypeOf(obj)odpytywania prototypu. Reflect.getPrototypeOf(obj)robi dokładnie to samo, z tą różnicą, że Reflectdefiniuje również odpowiedni Reflect.setPrototypeOf(obj, newProto)do ustawienia prototyp obiektu. Jest to nowy sposób aktualizowania prototypu obiektu, zgodny z ES6.
Należy pamiętać, że: setPrototypeOf również istnieje naObject (jak słusznie wskazał KNU „s komentarzu )!


EDYCJA:
Uwaga dodatkowa (adresowanie komentarzy do pytającego): Istnieje krótka i prosta odpowiedź na temat „P: Moduły ES6 a import HTML”, która wyjaśnia Realmsi przedstawia Loaderobiekty.

Inne wyjaśnienie można znaleźć pod tym linkiem :

Obiekt obszaru abstrakcyjnie wyodrębnia pojęcie odrębnego globalnego środowiska, z własnym globalnym obiektem, kopią standardowej biblioteki i „elementami wewnętrznymi” (standardowe obiekty, które nie są powiązane ze zmiennymi globalnymi, jak początkowa wartość Object.prototype).

Rozszerzalna sieć : jest to dynamiczny odpowiednik tego samego źródła <iframe>bez DOM.

Warto jednak wspomnieć: wszystko to jest wciąż w wersji roboczej, to nie jest specyfikacja wyryta w kamieniu! To ES6, więc pamiętaj o zgodności z przeglądarkami!

Mam nadzieję że to pomoże!

GitaarLAB
źródło
@Spencer Killen: Cóż… czy ta odpowiedź skierowała twoje myśli we właściwym kierunku i wyjaśnij, jak to się ma do różnicy między Reflect.apply i target.apply? Albo co powinienem dodać przed zakończeniem nagrody?
GitaarLAB
2
setPrototypeOf również istnieje w Object.
Knu
1
Zwróć uwagę, że użycie Reflect.getjako domyślnej implementacji dla proxy get nie działa dobrze, jeśli pośredniczysz w obiekcie z właściwościami prototypu. Po prostu narzeka, że ​​nie działa. Jednak jeśli zamiast tego użyjesz Reflect.get(target, property)bez przekazywania receiver, to zadziała.
CMCDragonkai
Moje testy, w których zawsze uzyskujesz dostęp do właściwości za pośrednictwem proxy, prowadzą do sytuacji, w której targetjest pierwotnym celem opakowanym przez proxy, podczas gdy receiverjest to samo proxy. Ale znowu może to wyglądać inaczej, jeśli uzyskasz dostęp do właściwości w inny sposób.
CMCDragonkai
5

Przechodząc przez projekt dokumentu znalezionego na wiki,

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

Otrzymujemy wiersz o „pojedynczym zwykłym przedmiocie”, który wyjaśnia w szkicu. Zawiera również definicje funkcji.

Witryna wiki powinna być wiarygodna, ponieważ link do niej można znaleźć na stronie internetowej emcascript

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

Znalazłem jednak pierwszy link przez Google i nie miałem szczęścia, aby go znaleźć, przeszukując bezpośrednio wiki.

niebieski
źródło
Dzięki, kroki podjęte, gdy każda metoda jest wywoływana, wymienione w oficjalnej specyfikacji, wydają się prawie takie same jak te w moim linku, ale nadal nie mogę dowiedzieć się, co robi każda metoda, gdy jest wywoływana, na przykład w Reflect. Zastosuj kroki są wymienione 4. Wykonaj operację abstrakcyjną PrepareForTailCall. 5. Zwróć wynik wywołania wewnętrznej metody [[Call]] celu z argumentami thisArgument i args ------- co to znaczy?
Jim Jones,
Patrząc na to, przypuszczam, że odwołuje się do rekurencji ogona w kroku 4, a krok 5 jest odniesieniem do funkcji prototypu. Wygląda na to, że ogólną ideą jest sprawdzenie, czy metoda Apply może działać na tym, do czego została zastosowana (kroki 1-2), uchwyt błędu (krok 3), a następnie wywołanie funkcji, dla której jest uruchamiana aplikacja Apply (kroki 4-5). Moje najlepsze przypuszczenie ze skanowania dokumentacji jest takie, że celem modułu Reflect jest wykonywanie funkcji, która wymaga jakiejś formy introspekcji obiektu. Użycie call jest również prawdopodobnie powodem, dla którego „nie ma wewnętrznej metody [[Call]]”.
Blue