Risposte:
La differenza di base è che una funzione di costruzione viene utilizzata con la newparola chiave (che fa sì che JavaScript crei automaticamente un nuovo oggetto, imposti thisla funzione su quell'oggetto e restituisca l'oggetto):
var objFromConstructor = new ConstructorFunction();
Una funzione di fabbrica è chiamata come una funzione "normale":
var objFromFactory = factoryFunction();
Ma per essere considerato una "fabbrica" dovrebbe restituire una nuova istanza di qualche oggetto: non la chiamereste una funzione "fabbrica" se restituisse un valore booleano o qualcosa del genere. Questo non accade automaticamente come connew , ma in alcuni casi consente una maggiore flessibilità.
In un esempio davvero semplice, le funzioni sopra menzionate potrebbero apparire in questo modo:
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;
}
Ovviamente puoi rendere le funzioni di fabbrica molto più complicate di quel semplice esempio.
Un vantaggio per le funzioni di fabbrica è quando l'oggetto da restituire potrebbe essere di diversi tipi a seconda di alcuni parametri.
someMethodgli oggetti restituiti dalla factory, ed è lì che diventa un po 'confuso. All'interno della funzione di fabbrica, se solo lo fa var obj = { ... , someMethod: function() {}, ... }, ciò porterebbe a ogni oggetto restituito contenere una copia diversa di someMethodciò che potremmo non desiderare. Ecco dove sarebbe utile usare newe prototypeall'interno della funzione di fabbrica.
newcon la funzione di costruzione; Ho pensato che fosse dove si potrebbe aver bisogno di vedere come sostituire i costruttori con l' esempio delle funzioni di fabbrica ed è qui che ho pensato che fosse necessaria la coerenza negli esempi. Comunque, la risposta è abbastanza istruttiva. Questo era solo un punto che volevo sollevare, non che sto abbassando la qualità della risposta in alcun modo.
newinternamente o utilizzare Object.create()per creare un oggetto con un prototipo specifico.
La maggior parte dei libri ti insegnano a usare costruttori e new
this si riferisce al nuovo oggetto
Ad alcune persone piace var myFoo = new Foo();leggere.
I dettagli dell'istanza vengono trapelati nell'API chiamante (tramite il newrequisito), quindi tutti i chiamanti sono strettamente associati all'implementazione del costruttore. Se hai mai bisogno dell'ulteriore flessibilità della fabbrica, dovrai riformattare tutti i chiamanti (è vero il caso eccezionale, piuttosto che la regola).
Dimenticare newè un bug così comune, dovresti prendere in seria considerazione l'aggiunta di un controllo plateplate per assicurarti che il costruttore sia chiamato correttamente (if (!(this instanceof Foo)) { return new Foo() } ). EDIT: da ES6 (ES2015) non puoi dimenticare newun classcostruttore, altrimenti il costruttore genererà un errore.
Se si esegue il instanceofcontrollo, si lascia l'ambiguità sulla necessità o meno new. Secondo me, non dovrebbe essere. Hai effettivamente cortocircuito ilnew requisito, il che significa che potresti cancellare lo svantaggio n. 1. Ma poi hai appena una funzione di fabbrica in tutto tranne il nome , con ulteriore boilerplate, una lettera maiuscola e un thiscontesto meno flessibile .
Ma la mia preoccupazione principale è che viola il principio aperto / chiuso. Inizi a esportare un costruttore, gli utenti iniziano a utilizzare il costruttore, quindi lungo la strada ti rendi conto che hai bisogno della flessibilità di una fabbrica, invece (ad esempio, per cambiare l'implementazione per utilizzare pool di oggetti o per creare un'istanza in contesti di esecuzione o per avere una maggiore flessibilità ereditaria utilizzando OO prototipo).
Sei bloccato, però. Non è possibile apportare la modifica senza rompere tutto il codice che chiama il costruttore connew . Ad esempio, non è possibile passare all'utilizzo di pool di oggetti per migliorare le prestazioni.
Inoltre, l'uso di costruttori ti dà un inganno instanceofche non funziona in contesti di esecuzione e non funziona se il tuo prototipo di costruttore viene scambiato. Fallirà anche se inizi a tornarethis dal tuo costruttore e poi passi all'esportazione di un oggetto arbitrario, cosa che dovresti fare per abilitare un comportamento di fabbrica nel tuo costruttore.
Meno codice - nessuna caldaia richiesta.
Puoi restituire qualsiasi oggetto arbitrario e utilizzare qualsiasi prototipo arbitrario, offrendoti una maggiore flessibilità per creare vari tipi di oggetti che implementano la stessa API. Ad esempio, un lettore multimediale in grado di creare istanze sia di lettori HTML5 che di lettori flash o una libreria di eventi in grado di emettere eventi DOM o eventi di socket Web. Le fabbriche possono anche creare istanze di oggetti in contesti di esecuzione, sfruttare pool di oggetti e consentire modelli di ereditarietà prototipali più flessibili.
Non avresti mai bisogno di convertirti da una fabbrica a un costruttore, quindi il refactoring non sarà mai un problema.
Nessuna ambiguità sull'uso new. Non farlo. (Farà thiscomportarsi male, vedi punto successivo).
thissi comporta normalmente - quindi è possibile utilizzarlo per accedere all'oggetto padre (ad esempio, all'interno player.create(), si thisriferisce player, proprio come farebbe qualsiasi altro metodo di invocazione. callE applyanche riassegnare this, come previsto. Se si memorizzano prototipi sull'oggetto padre, quello può essere un ottimo modo per scambiare dinamicamente funzionalità e abilitare un polimorfismo molto flessibile per l'istanza dell'oggetto.
Nessuna ambiguità sull'opportunità o meno di capitalizzare. Non farlo. Gli strumenti Lint si lamenteranno, quindi sarai tentato di provare a utilizzare newe quindi annullerai i vantaggi sopra descritti.
Ad alcune persone piace il modo var myFoo = foo();o var myFoo = foo.create();legge.
newnon si comporta come previsto (vedi sopra). Soluzione: non usarlo.
thisnon fa riferimento al nuovo oggetto (invece, se il costruttore viene invocato con notazione punto o notazione parentesi quadra, ad esempio foo.bar () - si thisriferisce a foo- proprio come ogni altro metodo JavaScript - vedere i vantaggi).
newviola il principio aperto / chiuso. Vedi medium.com/javascript-scene/… per una discussione molto più ampia di quella consentita da questi commenti.
newparola chiave, non credo che la newparola chiave in realtà fornisca alcuna leggibilità aggiuntiva. IMO, sembra sciocco saltare attraverso i cerchi per consentire ai chiamanti di digitare di più.
Un costruttore restituisce un'istanza della classe su cui lo chiami. Una funzione di fabbrica può restituire qualsiasi cosa. Si utilizzerà una funzione di fabbrica quando è necessario restituire valori arbitrari o quando una classe ha un processo di installazione di grandi dimensioni.
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
newcrea un oggetto prototipato User.prototypee chiama Usercon l'oggetto creato come thisvalore.
new considera un'espressione argomento per il suo operando come facoltativo:
let user = new User;
causerebbe newchiamare Usersenza argomenti.
newrestituisce l'oggetto che ha creato, a meno che il costruttore non restituisca un valore oggetto , che viene invece restituito. Questo è un caso limite che per la maggior parte può essere ignorato.
Gli oggetti creati dalle funzioni del costruttore ereditano le proprietà dalla proprietà del costruttore prototypee restituiscono true utilizzando l' instanceOfoperatore sulla funzione del costruttore.
I comportamenti sopra riportati possono non riuscire se si modifica dinamicamente il valore della prototypeproprietà del costruttore dopo averlo già utilizzato. Ciò è raro e non può essere modificato se il costruttore è stato creato utilizzando la classparola chiave.
Le funzioni del costruttore possono essere estese usando la extendsparola chiave.
Le funzioni del costruttore non possono essere restituite nullcome valore di errore. Poiché non è un tipo di dati oggetto, viene ignorato da new.
function User(name, age) {
return {
name,
age,
}
};
let user = User("Tom", 23);
Qui la funzione di fabbrica viene chiamata senza new. La funzione è interamente responsabile dell'uso diretto o indiretto se restituisce i suoi argomenti e il tipo di oggetto. In questo esempio restituisce un semplice [oggetto oggetto] con alcune proprietà impostate dagli argomenti.
Nasconde facilmente le complessità di implementazione della creazione di oggetti dal chiamante. Ciò è particolarmente utile per le funzioni di codice nativo in un browser.
La funzione di fabbrica non deve sempre restituire oggetti dello stesso tipo e potrebbe persino tornare nullcome indicatore di errore.
In casi semplici, le funzioni di fabbrica possono essere semplici nella struttura e nel significato.
Gli oggetti restituiti generalmente non ereditano dalla prototypeproprietà della funzione di fabbrica e restituiscono falseda instanceOf factoryFunction.
La funzione factory non può essere estesa in modo sicuro utilizzando la extendsparola chiave perché gli oggetti estesi erediteranno dalla prototypeproprietà delle funzioni di fabbrica anziché dalla prototypeproprietà del costruttore utilizzata dalla funzione di fabbrica.
Le fabbriche sono "sempre" migliori. Quindi, quando si usano lingue orientate agli oggetti
Le implementazioni (gli oggetti reali creati con nuovi) non sono esposte all'utente / consumatore della fabbrica. Ciò significa che lo sviluppatore della fabbrica può espandere e creare nuove implementazioni purché non violi il contratto ... e consente al consumatore della fabbrica di beneficiare della nuova API senza dover modificare il proprio codice ... se hanno usato una nuova e una "nuova" implementazione, allora devono andare e cambiare ogni riga che usa "nuova" per usare la "nuova" implementazione ... con la fabbrica il loro codice non cambia ...
Fabbriche - meglio di ogni altra cosa - la struttura a molla è completamente costruita attorno a questa idea.
Le fabbriche sono uno strato di astrazione e come tutte le astrazioni hanno un costo in complessità. Quando si incontra un'API basata sulla fabbrica, capire quale sia la fabbrica per una determinata API può essere una sfida per il consumatore dell'API. Con i costruttori la rilevabilità è banale.
Quando si decide tra medici e fabbriche è necessario decidere se la complessità è giustificata dal beneficio.
Vale la pena notare che i costruttori Javascript possono essere fabbriche arbitrarie restituendo qualcosa di diverso da questo o indefinito. Quindi in js puoi ottenere il meglio da entrambi i mondi: API rilevabili e pool di oggetti / cache.
new, Modifica del comportamento di this, Modifica del valore di ritorno, Connessione di un riferimento prototipo, Abilitazione instanceof(che si trova e non deve essere utilizzata per questo scopo). Apparentemente, tutte queste sono "caratteristiche". In pratica, danneggiano la qualità del tuo codice.