Odpowiedzi:
Podstawowa różnica polega na tym, że funkcja konstruktora jest używana ze new
słowem kluczowym (co powoduje, że JavaScript automatycznie tworzy nowy obiekt, ustawia this
w 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.
someMethod
obiektó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ę, someMethod
której moglibyśmy nie chcieć. W tym miejscu pomogłoby użycie new
i prototype
wewnątrz funkcji fabrycznej.
new
funkcji 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.
new
wewnę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 new
wymaganie), 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 new
jest 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ć new
o class
konstruktorze, w przeciwnym razie konstruktor zgłosi błąd.
Jeśli to zrobisz instanceof
, pozostawia to niejasność co do tego, czy new
jest to wymagane. Moim zdaniem nie powinno. Skutecznie zwarłeś new
wymagania, 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 this
kontekstem.
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, instanceof
któ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ć this
z 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 this
złe zachowanie, patrz następny punkt).
this
zachowuje się jak zazwyczaj - tak można go użyć do uzyskania dostępu do obiektu nadrzędnego (na przykład wewnątrz player.create()
, this
odnosi się do player
., tak jak każdy inny wywołania metoda byłaby call
i apply
ró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.
new
nie zachowuje się zgodnie z oczekiwaniami (patrz wyżej). Rozwiązanie: nie używaj go.
this
nie odwołuje się do nowego obiektu (zamiast tego, jeśli konstruktor jest wywoływany z notacją z kropką lub nawiasem kwadratowym, np. foo.bar () - this
odnosi się foo
- jak każda inna metoda JavaScript - zobacz korzyści).
new
narusza zasadę otwarcia / zamknięcia. Zajrzyj na medium.com/javascript-scene/…, aby uzyskać dużo szerszą dyskusję, niż pozwalają na to te komentarze.
new
słowa kluczowego, nie sądzę, aby new
sł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");
new
tworzy prototyp obiektu User.prototype
i wywołuje User
utworzony obiekt jako jego this
wartość.
new
traktuje wyrażenie argumentu dla swojego operandu jako opcjonalne:
let user = new User;
spowodowałoby new
wywołanie User
bez argumentów.
new
zwraca 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 prototype
i zwracają wartość true przy użyciu instanceOf
operatora funkcji konstruktora.
Powyższe zachowania mogą się nie powieść, jeśli dynamicznie zmienisz wartość właściwości konstruktora prototype
po wcześniejszym użyciu konstruktora. Jest to rzadkie i nie można go zmienić, jeśli konstruktor został utworzony przy użyciu class
słowa kluczowego.
Funkcje konstruktora można rozszerzyć za pomocą extends
słowa kluczowego.
Funkcje konstruktora nie mogą zwracać null
wartoś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ć null
jako 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 prototype
i wracają false
z instanceOf factoryFunction
.
Funkcji fabrycznej nie można bezpiecznie rozszerzyć za pomocą extends
słowa kluczowego, ponieważ rozszerzone obiekty będą dziedziczyć z prototype
właściwości funkcji fabryki, a nie z prototype
wł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.