Odpowiedzi:
Podstawowa różnica polega na tym, że funkcja konstruktora jest używana ze newsłowem kluczowym (co powoduje, że JavaScript automatycznie tworzy nowy obiekt, ustawia thisw funkcji na ten obiekt i zwraca obiekt):
var objFromConstructor = new ConstructorFunction();
Funkcja fabryczna nazywana jest „zwykłą” funkcją:
var objFromFactory = factoryFunction();
Ale aby można było uznać ją za „fabrykę”, musiałby zwrócić nową instancję jakiegoś obiektu: nie nazwałbyś jej funkcją „fabryczną”, gdyby po prostu zwracała wartość logiczną lub coś w tym rodzaju. Nie dzieje się to automatycznie, jak w przypadku new, ale w niektórych przypadkach pozwala na większą elastyczność.
W naprawdę prostym przykładzie funkcje wymienione powyżej mogą wyglądać mniej więcej tak:
function ConstructorFunction() {
this.someProp1 = "1";
this.someProp2 = "2";
}
ConstructorFunction.prototype.someMethod = function() { /* whatever */ };
function factoryFunction() {
var obj = {
someProp1 : "1",
someProp2 : "2",
someMethod: function() { /* whatever */ }
};
// other code to manipulate obj in some way here
return obj;
}
Oczywiście możesz znacznie bardziej skomplikować funkcje fabryczne niż ten prosty przykład.
Jedną z zalet funkcji fabrycznych jest sytuacja, gdy zwracany obiekt może mieć kilka różnych typów w zależności od niektórych parametrów.
someMethodobiektów zwróconych przez fabrykę i wtedy robi się trochę mglisty. Wewnątrz funkcji fabrycznej, jeśli tylko to zrobi var obj = { ... , someMethod: function() {}, ... }, doprowadzi to do tego, że każdy zwracany obiekt będzie zawierał inną kopię, someMethodktórej moglibyśmy nie chcieć. W tym miejscu pomogłoby użycie newi prototypewewnątrz funkcji fabrycznej.
newfunkcji konstruktora; Pomyślałem, że w tym miejscu może zaistnieć potrzeba zastąpienia konstruktorów przykładem funkcji fabrycznych i pomyślałem, że wymagana jest spójność przykładów. W każdym razie odpowiedź jest wystarczająco pouczająca. To była tylko kwestia, którą chciałem poruszyć, a nie to, że w jakikolwiek sposób obniżam jakość odpowiedzi.
newwewnętrznie lub używać Object.create()do tworzenia obiektu z określonym prototypem.
Większość książek uczy, jak używać konstruktorów i new
this odnosi się do nowego obiektu
Niektórzy ludzie lubią sposób var myFoo = new Foo();czytać.
Szczegóły tworzenia wystąpień wyciekają do wywołującego interfejsu API (poprzez newwymaganie), więc wszystkie wywołujące są ściśle powiązane z implementacją konstruktora. Jeśli kiedykolwiek będziesz potrzebować dodatkowej elastyczności fabryki, będziesz musiał refaktoryzować wszystkich dzwoniących (trzeba przyznać, że jest to wyjątkowy przypadek, a nie reguła).
Zapominanie newjest tak częstym błędem, należy zdecydowanie rozważyć dodanie standardowego sprawdzenia, aby upewnić się, że konstruktor jest wywoływany poprawnie ( if (!(this instanceof Foo)) { return new Foo() }). EDYCJA: Od ES6 (ES2015) nie można zapomnieć newo classkonstruktorze, w przeciwnym razie konstruktor zgłosi błąd.
Jeśli to zrobisz instanceof, pozostawia to niejasność co do tego, czy newjest to wymagane. Moim zdaniem nie powinno. Skutecznie zwarłeś newwymagania, co oznacza, że możesz usunąć wadę # 1. Ale masz po prostu funkcję fabryki , z wyjątkiem nazwy , z dodatkowym schematem, wielką literą i mniej elastycznym thiskontekstem.
Ale moim głównym zmartwieniem jest to, że narusza zasadę otwartego / zamkniętego. Zaczynasz od eksportowania konstruktora, użytkownicy zaczynają używać konstruktora, a następnie po drodze zdajesz sobie sprawę, że zamiast tego potrzebujesz elastyczności fabryki (na przykład, aby przełączyć implementację na używanie pul obiektów, tworzenie instancji w kontekstach wykonania lub mają większą elastyczność dziedziczenia dzięki prototypowemu obiektowi zewnętrznemu).
Ale utkniesz. Nie możesz dokonać zmiany bez zerwania całego kodu, który wywołuje twój konstruktor new. Na przykład nie można przełączyć się na używanie pul obiektów w celu zwiększenia wydajności.
Ponadto używanie konstruktorów daje oszustwo, instanceofktóre nie działa w różnych kontekstach wykonania i nie działa, jeśli twój prototyp konstruktora zostanie wymieniony. Nie powiedzie się również, jeśli zaczniesz wracać thisz konstruktora, a następnie przełączysz się na eksportowanie dowolnego obiektu, co musisz zrobić, aby włączyć zachowanie fabryczne w konstruktorze.
Mniej kodu - nie jest wymagany szablon.
Możesz zwrócić dowolny obiekt i użyć dowolnego prototypu - co daje większą elastyczność przy tworzeniu różnych typów obiektów, które implementują to samo API. Na przykład odtwarzacz multimedialny, który może tworzyć instancje odtwarzaczy HTML5 i Flash, lub bibliotekę zdarzeń, która może emitować zdarzenia DOM lub zdarzenia gniazd sieciowych. Fabryki mogą również tworzyć instancje obiektów w kontekstach wykonywania, wykorzystywać pule obiektów i pozwalać na bardziej elastyczne prototypowe modele dziedziczenia.
Nigdy nie musiałbyś przechodzić z fabryki na konstruktora, więc refaktoryzacja nigdy nie będzie problemem.
Brak dwuznaczności w używaniu new. Nie. (Spowoduje to thiszłe zachowanie, patrz następny punkt).
thiszachowuje się jak zazwyczaj - tak można go użyć do uzyskania dostępu do obiektu nadrzędnego (na przykład wewnątrz player.create(), thisodnosi się do player., tak jak każdy inny wywołania metoda byłaby calli applyrównież Przypisz this, zgodnie z oczekiwaniami Jeśli przechowujesz prototypy na obiekcie nadrzędnym, że. może być świetnym sposobem na dynamiczną wymianę funkcjonalności i włączenie bardzo elastycznego polimorfizmu do tworzenia instancji obiektu.
Brak dwuznaczności co do tego, czy kapitalizować. Nie. Narzędzia kłaczków będą narzekać, a wtedy będziesz kuszony, aby spróbować ich użyć new, a wtedy cofniesz korzyści opisane powyżej.
Niektórzy ludzie lubią sposób var myFoo = foo();lub var myFoo = foo.create();czyta.
newnie zachowuje się zgodnie z oczekiwaniami (patrz wyżej). Rozwiązanie: nie używaj go.
thisnie odwołuje się do nowego obiektu (zamiast tego, jeśli konstruktor jest wywoływany z notacją z kropką lub nawiasem kwadratowym, np. foo.bar () - thisodnosi się foo- jak każda inna metoda JavaScript - zobacz korzyści).
newnarusza zasadę otwarcia / zamknięcia. Zajrzyj na medium.com/javascript-scene/…, aby uzyskać dużo szerszą dyskusję, niż pozwalają na to te komentarze.
newsłowa kluczowego, nie sądzę, aby newsłowo kluczowe faktycznie zapewniało jakąkolwiek dodatkową czytelność. IMO, wydaje się głupie przeskakiwanie przez obręcze, aby umożliwić dzwoniącym pisanie więcej.
Konstruktor zwraca instancję klasy, do której został wywołany. Funkcja fabryczna może zwrócić wszystko. Funkcji fabrycznej można użyć, gdy trzeba zwrócić dowolne wartości lub gdy klasa ma duży proces konfiguracji.
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
newtworzy prototyp obiektu User.prototypei wywołuje Userutworzony obiekt jako jego thiswartość.
new traktuje wyrażenie argumentu dla swojego operandu jako opcjonalne:
let user = new User;
spowodowałoby newwywołanie Userbez argumentów.
newzwraca utworzony przez siebie obiekt, chyba że konstruktor zwraca wartość obiektu , która jest zamiast tego zwracana. Jest to skrajny przypadek, który w większości można zignorować.
Obiekty utworzone przez funkcje konstruktora dziedziczą właściwości z właściwości konstruktora prototypei zwracają wartość true przy użyciu instanceOfoperatora funkcji konstruktora.
Powyższe zachowania mogą się nie powieść, jeśli dynamicznie zmienisz wartość właściwości konstruktora prototypepo wcześniejszym użyciu konstruktora. Jest to rzadkie i nie można go zmienić, jeśli konstruktor został utworzony przy użyciu classsłowa kluczowego.
Funkcje konstruktora można rozszerzyć za pomocą extendssłowa kluczowego.
Funkcje konstruktora nie mogą zwracać nullwartości błędu. Ponieważ nie jest to typ danych obiektowych, jest ignorowany przez new.
function User(name, age) {
return {
name,
age,
}
};
let user = User("Tom", 23);
Tutaj wywoływana jest funkcja fabryczna bez new. Funkcja jest całkowicie odpowiedzialna za bezpośrednie lub pośrednie użycie, jeśli jej argumenty i typ zwracanego obiektu. W tym przykładzie zwraca prosty [obiekt Object] z niektórymi właściwościami ustawionymi na podstawie argumentów.
Z łatwością ukrywa przed wywołującym złożoność implementacji tworzenia obiektów. Jest to szczególnie przydatne w przypadku natywnych funkcji kodu w przeglądarce.
Funkcja fabryczna nie zawsze musi zwracać obiekty tego samego typu, a może nawet zwracać nulljako wskaźnik błędu.
W prostych przypadkach funkcje fabryczne mogą mieć prostą strukturę i znaczenie.
Zwracane obiekty zazwyczaj nie dziedziczą z właściwości funkcji fabryki prototypei wracają falsez instanceOf factoryFunction.
Funkcji fabrycznej nie można bezpiecznie rozszerzyć za pomocą extendssłowa kluczowego, ponieważ rozszerzone obiekty będą dziedziczyć z prototypewłaściwości funkcji fabryki, a nie z prototypewłaściwości konstruktora używanego przez funkcję fabryki.
Fabryki są „zawsze” lepsze. W takim razie podczas używania języków obiektowych
Implementacje (rzeczywiste obiekty utworzone za pomocą nowego) nie są ujawniane fabrycznemu użytkownikowi / konsumentowi. Oznacza to, że deweloper fabryki może rozwijać i tworzyć nowe implementacje, o ile nie zerwie umowy ... i pozwala fabrycznemu konsumentowi po prostu korzystać z nowego interfejsu API bez konieczności zmiany kodu ... jeśli użyli nowej i pojawiła się „nowa” implementacja, muszą przejść i zmienić każdą linię, która używa „nowej”, aby użyć „nowej” implementacji ... w fabryce ich kod się nie zmienia ...
Fabryki - lepsze niż wszystko inne - ramy wiosenne są całkowicie zbudowane wokół tego pomysłu.
Fabryki są warstwą abstrakcji i jak wszystkie abstrakcje mają koszt w złożoności. W przypadku napotkania fabrycznego interfejsu API ustalenie, jaka jest fabryka dla danego interfejsu API, może stanowić wyzwanie dla konsumenta API. W przypadku konstruktorów wykrywalność jest banalna.
Decydując się między lekarzami a fabrykami, musisz zdecydować, czy złożoność jest uzasadniona korzyścią.
Warto zauważyć, że konstruktory Javascript mogą być dowolnymi fabrykami, zwracając coś innego niż this lub undefined. Tak więc w js możesz uzyskać to, co najlepsze z obu światów - wykrywalny interfejs API i buforowanie / buforowanie obiektów.
new, zmianę zachowania this, zmianę wartości zwracanej, podłączenie referencji prototypu, włączanie instanceof(co leży i nie powinno być używane do tego celu). Na pozór wszystko to są „funkcje”. W praktyce szkodzą one jakości kodu.