ES6 umożliwia rozszerzenie specjalnych obiektów. Więc możliwe jest dziedziczenie z funkcji. Taki obiekt można wywołać jako funkcję, ale jak mogę zaimplementować logikę dla takiego wywołania?
class Smth extends Function {
constructor (x) {
// What should be done here
super();
}
}
(new Smth(256))() // to get 256 at this call?
Każda metoda klasy otrzymuje odwołanie do instancji klasy za pośrednictwem this
. Ale kiedy jest wywoływany jako funkcja, this
odnosi się do window
. Jak mogę uzyskać odwołanie do instancji klasy, gdy jest wywoływana jako funkcja?
PS: To samo pytanie po rosyjsku.
super(x)
(tj. Przekaż to dalejFunction
)? Nie jestem pewien, czyFunction
rzeczywiście można go przedłużyć.Error
m.in.Function
jest to po prostu konstruktor funkcji. Implementację funkcji należy przekazać konstruktorowi. Jeśli nie chceszSmth
akceptować implementacji, musisz podać ją w konstruktorze, tjsuper('function implementation here')
.Function
konstruktora (środowiska wykonawczego), który bardzo różni się od wyrażenia funkcyjnego (składni).Odpowiedzi:
super
Wezwanie wywołaFunction
konstruktor, który oczekuje ciąg kodu. Jeśli chcesz uzyskać dostęp do danych instancji, możesz po prostu zakodować je na stałe:ale to nie jest satysfakcjonujące. Chcemy użyć zamknięcia.
Posiadanie zwróconej funkcji jako zamknięcia, które może uzyskać dostęp do zmiennych instancji , jest możliwe, ale nie jest łatwe. Dobrą rzeczą jest to, że nie musisz dzwonić,
super
jeśli nie chcesz - nadal możeszreturn
dowolne obiekty z konstruktorów klasy ES6. W takim przypadku zrobilibyśmyAle możemy zrobić jeszcze lepiej i wyabstrahować to z
Smth
:Wprawdzie stwarza to dodatkowy poziom pośrednictwa w łańcuchu dziedziczenia, ale to niekoniecznie jest złe (możesz go rozszerzyć zamiast natywnego
Function
). Jeśli chcesz tego uniknąć, użyjale zauważ, że
Smth
nie odziedziczy dynamicznieFunction
właściwości statycznych .źródło
Smth
instancje byłyinstanceof Smth
(tak jak wszyscy by się tego spodziewali). Możesz pominąćObject.setPrototypeOf
wywołanie, jeśli nie potrzebujesz tej lub żadnej z metod prototypowych zadeklarowanych w Twojej klasie.Object.setPrototypeOf
nie jest to duże zagrożenie optymalizacyjne, o ile jest wykonywane zaraz po utworzeniu obiektu. Tylko wtedy, gdy mutujesz [[prototyp]] obiektu w tę iz powrotem podczas jego życia, będzie on zły.this
ireturn
obiektu.Jest to podejście do tworzenia wywoływalnych obiektów, które poprawnie odwołują się do swoich składowych obiektów i zachowują prawidłowe dziedziczenie, bez ingerowania w prototypy.
Po prostu:
Rozszerz tę klasę i dodaj
__call__
metodę, więcej poniżej ...Wyjaśnienie w kodzie i komentarzach:
Zobacz na repl.it
Dalsze wyjaśnienie
bind
:function.bind()
działa podobniefunction.call()
i mają podobny podpis metody:fn.call(this, arg1, arg2, arg3, ...);
więcej na mdnfn.bind(this, arg1, arg2, arg3, ...);
więcej na mdnW obu przypadkach pierwszy argument przedefiniowuje
this
kontekst wewnątrz funkcji. Dodatkowe argumenty mogą być również powiązane z wartością. Ale tam, gdziecall
natychmiast wywołuje funkcję z powiązanymi wartościami,bind
zwraca „egzotyczny” obiekt funkcji, który w przezroczysty sposób zawija oryginał, zthis
ustawionymi wszystkimi argumentami.Więc kiedy definiujesz funkcję, to
bind
niektóre z jej argumentów:Wywołujesz funkcję powiązaną z tylko pozostałymi argumentami, jej kontekst jest wstępnie ustawiony, w tym przypadku jako
['hello']
.źródło
bind
działa (tj. Dlaczego zwraca wystąpienieExFunc
)?bind
zwraca przezroczysty obiekt funkcyjny, który otacza obiekt funkcji, na którym został wywołany, czyli nasz wywoływalny obiekt, po prostu zthis
odbiciem kontekstu. Więc naprawdę zwraca przezroczysto opakowaną instancjęExFunc
. Post zaktualizowany o więcej informacjibind
.constructor
pobind
wExFunc
. W podklasach ExFunc wszyscy członkowie są dostępni. Jeśli chodzi oinstanceof
; w es6 funkcje powiązane są określane jako egzotyczne, więc ich wewnętrzne działanie nie jest widoczne, ale myślę, że przekazuje wywołanie do swojego opakowanego celu za pośrednictwemSymbol.hasInstance
. Przypomina proxy, ale jest prostą metodą osiągnięcia pożądanego efektu. Ich podpis jest podobny, a nie to samo.__call__
mam dostęputhis.a
lubthis.ab()
. np repl.it/repls/FelineFinishedDesktopenvironmentMożesz opakować instancję Smth w proxy z
apply
(i być możeconstruct
) pułapką:źródło
this
nadal jest pustą funkcją (sprawdźnew Smth().toString()
).setPrototypeOf
i nie mówi nic o serwerach proxy. Ale wydaje mi się, że serwery proxy mogą być równie problematyczne jaksetPrototypeOf
. Co więcejtoString
, można go umieścić w cieniu za pomocą niestandardowej metody w programieSmth.prototype
. Natywny jest i tak zależny od implementacji.construct
pułapkę, aby określić zachowanienew new Smth(256)()
. I dodaj niestandardowe metody, które przesłaniają natywne metody, które uzyskują dostęp do kodu funkcji, jaktoString
zauważył Bergi.apply
metoda została zaimplementowana w sposób, w jaki ma być używana, czy jest to tylko demonstracja i muszę przejrzeć więcej informacji na jej tematProxy
iReflect
właściwie z niej korzystać?Wziąłem radę z odpowiedzi Bergi i zawarłem ją w moduł NPM .
źródło
Aktualizacja:
Niestety to nie do końca działa, ponieważ zwraca teraz obiekt funkcji zamiast klasy, więc wydaje się, że nie można tego zrobić bez modyfikacji prototypu. Kulawy.
Zasadniczo problem polega na tym, że nie ma możliwości ustawienia
this
wartości dlaFunction
konstruktora. Jedynym sposobem, aby to naprawdę zrobić, byłoby użycie.bind
metody później, jednak nie jest to zbyt przyjazne dla klas.Moglibyśmy to zrobić w pomocniczej klasie bazowej, jednak
this
staje się ona dostępna dopiero po pierwszymsuper
wywołaniu, więc jest to trochę trudne.Przykład roboczy:
(Przykład wymaga nowoczesnej przeglądarki lub
node --harmony
.)Zasadniczo funkcja podstawowa
ClassFunction
extends otaczaFunction
wywołanie konstruktora niestandardową funkcją, która jest podobna do.bind
, ale umożliwia późniejsze powiązanie przy pierwszym wywołaniu. Następnie w samymClassFunction
konstruktorze wywołuje zwróconą funkcję, zsuper
której jest teraz powiązana funkcja, przechodzącthis
do zakończenia konfigurowania niestandardowej funkcji wiązania.To wszystko jest dość skomplikowane, ale pozwala uniknąć mutacji prototypu, który jest uważany za zły z powodów optymalizacji i może generować ostrzeżenia w konsolach przeglądarek.
źródło
bound
będzie odnosić się do funkcji, którą maszreturn
z tej anonimowej klasy. Po prostu nazwij go i odnieś się bezpośrednio do niego. Zalecałbym również unikanie przekazywania ciągów kodu, są one po prostu bałaganem do pracy (na każdym etapie procesu programowania).extends
Wydaje się, że to nie działa zgodnie z oczekiwaniami,Function.isPrototypeOf(Smth)
a takżenew Smth instanceof Function
jest fałszywe.console.log((new Smth) instanceof Function);
jesttrue
dla mnie w Node v5.11.0 i najnowszym Firefoksie.new Smth instanceof Smth
nie działa z twoim rozwiązaniem. Również żadne metody nieSmth
będą dostępne w twoich instancjach - po prostu zwracasz standardFunction
, a nie plikSmth
.extend Function
równieżnew Smth instanceof Smth
fałszuje.Najpierw doszedłem do rozwiązania
arguments.callee
, ale było okropne.Spodziewałem się, że zepsuje się w globalnym trybie ścisłym, ale wydaje się, że działa nawet tam.
To był zły sposób z powodu używania
arguments.callee
, przekazywania kodu jako łańcucha i wymuszania jego wykonania w trybie nieścisłym. Ale potemapply
pojawił się pomysł do zastąpienia .A test pokazujący, że jestem w stanie uruchomić to jako funkcję na różne sposoby:
Wersja z
w rzeczywistości zawiera
bind
funkcjonalność:Wersja z
robi
call
iapply
nawindow
niespójnych:więc czek należy przenieść do
apply
:źródło
this
jest oknem, nie jest niezdefiniowana, więc utworzona funkcja nie jest w trybie ścisłym (przynajmniej w chrome).Function
nie jest w trybie ścisłym. Choć okropne, jest interesujące +1. Jednak prawdopodobnie nie byłbyś w stanie przejść dalej łańcucha.To rozwiązanie, które wypracowałem, spełnia wszystkie moje potrzeby związane z rozszerzaniem funkcji i całkiem dobrze mi służyło. Zalety tej techniki to:
ExtensibleFunction
kod jest idiomatyczny z rozszerzaniem dowolnej klasy ES6 (nie, grzebanie w udawanych konstruktorach lub serwerach proxy).instanceof
/.constructor
zwraca oczekiwane wartości..bind()
.apply()
i.call()
wszystkie działają zgodnie z oczekiwaniami. Odbywa się to poprzez nadpisanie tych metod w celu zmiany kontekstu funkcji „wewnętrznej” w przeciwieństwie doExtensibleFunction
instancji (lub jej podklasy)..bind()
zwraca nowe wystąpienie konstruktora funkcji (ExtensibleFunction
czy to lub podklasy). UżywaObject.assign()
do zapewnienia, że właściwości przechowywane w powiązanej funkcji są zgodne z właściwościami funkcji źródłowej.Symbol
, który można zaciemnić za pomocą modułów lub IIFE (lub dowolnej innej powszechnej techniki prywatyzacji odniesień).I bez zbędnych ceregieli kod:
Edytować
Ponieważ byłem w nastroju, pomyślałem, że opublikuję pakiet na ten temat na npm.
źródło
Istnieje proste rozwiązanie, które wykorzystuje możliwości funkcjonalne JavaScript: Przekaż "logikę" jako argument funkcji do konstruktora swojej klasy, przypisz metody tej klasy do tej funkcji, a następnie zwróć tę funkcję z konstruktora jako wynik :
Powyższe zostało przetestowane na Node.js 8.
Wadą powyższego przykładu jest to, że nie obsługuje on metod dziedziczonych z łańcucha nadklasy. Aby to obsługiwać, po prostu zamień „Object. GetOwnPropertyNames (...)” na coś, co zwraca również nazwy dziedziczonych metod. Jak to zrobić, moim zdaniem, jest wyjaśnione w innym pytaniu-odpowiedzi na Stack Overflow :-). BTW. Byłoby miło, gdyby ES7 dodało metodę do tworzenia dziedziczonych nazw metod ;-).
Jeśli potrzebujesz obsługi dziedziczonych metod, jedną z możliwości jest dodanie metody statycznej do powyższej klasy, która zwraca wszystkie odziedziczone i lokalne nazwy metod. Następnie wywołaj to z konstruktora. Jeśli następnie rozszerzysz tę klasę Funk, otrzymasz również dziedziczoną metodę statyczną.
źródło