Prima di ES6, c’era molta confusione sulle differenze tra una funzione factory e una funzione constructor in JavaScript. Poiché ES6 ha la parola chiave ‘class’, molte persone sembrano pensare che abbia risolto molti problemi con le funzioni del costruttore. Esploriamo le principali differenze di cui devi ancora essere consapevole.,
Innanzitutto, diamo un’occhiata ad esempi di ciascuno:
Ognuna di queste strategie memorizza i metodi su un prototipo condiviso e opzionalmente supporta i dati privati tramite chiusure di funzioni del costruttore. In altre parole, hanno per lo più le stesse caratteristiche, e potrebbe essere utilizzato principalmente in modo intercambiabile.
In JavaScript, qualsiasi funzione può restituire un nuovo oggetto. Quando non è una funzione o una classe del costruttore, si chiama una funzione di fabbrica.,
ES6 classes desugar to constructor functions, quindi tutto ciò che segue sulle funzioni del costruttore si applica anche alle classi ES6:
class Foo {}console.log(typeof Foo); // function
I costruttori forzano i chiamanti a usare la parola chiave new
. Le fabbriche no, tutto qui, ma ha degli effetti collaterali rilevanti.
Quindi cosa fa la parola chiavenew
?,
Nota: Useremo
instance
per fare riferimento all’istanza appena creata eConstructor
per fare riferimento alla funzione o alla classe del costruttore che ha creato l’istanza.
- Crea un’istanza di un nuovo oggetto di istanza e lega
this
ad esso all’interno del costruttore. - Lega
instance.__proto__
aConstructor.prototype
. - Come effetto collaterale di 2, lega
instance.__proto__.constructor
aConstructor
., - restituisce implicitamente
this
, che fa riferimento ainstance
.
Vantaggi dei costruttori& `class`
- La maggior parte dei libri ti insegna a usare classe o costruttori.
-
this
si riferisce al nuovo oggetto. - Ad alcune persone piace il modo in cui
myFoo = new Foo()
legge. - Potrebbe esserci un vantaggio in termini di prestazioni di micro-ottimizzazione, ma non dovresti preoccuparti di questo a meno che tu non abbia profilato il tuo codice e dimostrato che è un problema per te.,
Svantaggi dei costruttori& `class`
Prima di ES6, dimenticandonew
era un bug molto comune. Per contrastarlo, molte persone hanno usato boilerplate per applicarlo:
function Foo() {
if (!(this instanceof Foo)) { return new Foo(); }
}
In ES6+ (ES2015) se si tenta di chiamare un costruttore di classi senzanew
, genererà sempre un errore. Non è possibile evitare di forzare il requisitonew
sui chiamanti senza avvolgere la classe in una funzione factory.,
I dettagli dell’istanziazione vengono trapelati nell’API chiamante (tramite il requisito “nuovo”).
Tutti i chiamanti sono strettamente accoppiati all’implementazione del costruttore. Se avete mai bisogno della flessibilità aggiuntiva della fabbrica, il refactor è un cambiamento di rottura. I refactoring da classe a fabbrica sono abbastanza comuni da apparire nel libro di Refactoring seminale, ” Refactoring: Improving the Design of Existing Code” di Martin Fowler, Kent Beck, John Brant, William Opdyke e Don Roberts.,
I costruttori infrangono il principio aperto/chiuso
A causa del requisitonew
, le funzioni dei costruttori violano il principio aperto / chiuso: un’API dovrebbe essere aperta per estensione, ma chiusa per modifica.
Sostengo che il refactor da classe a factory è abbastanza comune da essere considerato un’estensione standard per tutti i costruttori: l’aggiornamento da una classe a una factory non dovrebbe rompere le cose, ma in JavaScript, lo fa.,
Se inizi a esportare un costruttore o una classe e gli utenti iniziano a utilizzare il costruttore, in fondo alla strada ti rendi conto che hai bisogno della flessibilità di una factory, (ad esempio, per cambiare l’implementazione per usare pool di oggetti, o per istanziare tra i contesti di esecuzione, o per avere più flessibilità di ereditarietà usando prototipi alternativi), non puoi farlo facilmente senza forzare un refactor sui chiamanti.,
Purtroppo, in JavaScript, il passaggio da un costruttore o una classe di una fabbrica è una modifica di rilievo:
Nell’esempio di cui sopra, cominciamo con una classe, ma vogliamo aggiungere la capacità di offrire diversi tipi di auto in bundle. Per fare ciò, la fabbrica utilizza prototipi alternativi per diversi pacchetti di auto. Ho usato questa tecnica per memorizzare varie implementazioni di un’interfaccia media player, scegliendo il prototipo corretto in base al tipo di supporto che il lettore doveva controllare.,
L’uso dei costruttori abilita l’ingannevole `instanceof`
Una delle modifiche di rottura nel costruttore al refactor di fabbrica èinstanceof
. A volte le persone sono tentate di usare instanceof
come guardia di controllo del tipo nel loro codice. Questo può essere molto problematico. Ti consiglio di evitare instanceof
.
`instanceof` bugie.,
instanceof
non esegue il controllo del tipo nel modo in cui ci si aspetta controlli simili da fare in lingue fortemente tipizzate. Invece, esegue un controllo di identità confrontando l’oggetto__proto__
con la proprietàConstructor.prototype
.
Non funzionerà su diversi reami di memoria come gli iframe, ad esempio (una fonte comune di bug in JavaScript 3rd party incorpora). Inoltre, non funziona se il tuo Constructor.prototype
viene sostituito.,
Si avrà esito negativo anche se si inizia con una classe o di un costruttore (che restituisce this
, legato alla Constructor.prototype
), e quindi passare per l’esportazione di un oggetto arbitrario (non collegata ad un Constructor.prototype
), che è ciò che accade quando si passa da un costruttore per una fabbrica.
In breve, instanceof
è un altro modo in cui passare da un costruttore a una fabbrica è un cambiamento di rottura.
Vantaggi dell’utilizzo della classe
- Sintassi comoda e autonoma.,
- Un singolo modo canonico per emulare le classi in JavaScript. Prima di ES6, c’erano diverse implementazioni concorrenti nelle librerie popolari.
- Più familiare alle persone da un background linguistico basato sulla classe.
Inconvenienti dell’uso della classe
Tutti gli inconvenienti del costruttore, oltre a:
- Tentazione per gli utenti di creare gerarchie di classi problematiche usando la parola chiave
extends
.,
Le gerarchie di classe portano a una serie di problemi ben noti nella progettazione orientata agli oggetti, tra cui il fragile problema della classe base, il problema della banana gorilla, la duplicazione per necessità e così via. Sfortunatamente, la classe offre estende come palle permettersi lancio e sedie permettersi seduta. Per ulteriori informazioni, leggi “I due pilastri di JavaScript: Prototypal OO” e “Inside the Dev Team Death Spiral”.,
Vale la pena notare che sia i costruttori che le fabbriche possono anche essere utilizzati per creare gerarchie di ereditarietà problematiche, ma con la parola chiave `extends`, class crea un’accessibilità che ti porta sulla strada sbagliata. In altre parole, ti incoraggia a pensare in termini di relazioni inflessibili (e spesso sbagliate) is-a, piuttosto che le relazioni compositive più flessibili has-a o can-do.
Un affordance è una caratteristica che offre la possibilità di eseguire una determinata azione., Ad esempio, una manopola consente di torcere, una leva consente di tirare, un pulsante consente di premere, ecc.
Vantaggi dell’utilizzo delle fabbriche
Le fabbriche sono molto più flessibili delle funzioni o delle classi del costruttore e non portano le persone sulla strada sbagliata tentandole con la parola chiave `estende` e Esistono molti meccanismi di riutilizzo del codice più sicuri che dovresti favorire rispetto all’ereditarietà della classe, incluse funzioni e moduli.,
Restituisce qualsiasi oggetto arbitrario e utilizza qualsiasi prototipo arbitrario
Ad esempio, è possibile creare facilmente vari tipi di oggetti che implementano la stessa API, ad esempio un lettore multimediale che può istanziare i lettori per più tipi di contenuti video che utilizzano API diverse sotto il cofano, o una libreria di eventi che può emettere eventi DOM o eventi
Le fabbriche possono anche istanziare oggetti in contesti di esecuzione, sfruttare i pool di oggetti e consentire modelli di ereditarietà prototipali più flessibili.,
Nessuna preoccupazione di refactoring
Non avresti mai bisogno di convertire da una fabbrica a un costruttore, quindi il refactoring non sarà mai un problema.
Nessun `nuovo`
Nessuna ambiguità sull’uso dinew
. Non farlo. (Farà in modo che this
si comporti male, vedi il prossimo punto).
Standard `questo` comportamento
this
si comporta come farebbe normalmente, quindi puoi usarlo per accedere all’oggetto genitore. Ad esempio, all’interno di player.create()
, this
si riferisce al lettore, proprio come farebbe qualsiasi altro metodo di invocazione., call()
e apply()
riassegnano anche this
come previsto.
Ad alcune persone piace il modo in cui `myFoo = createFoo()` legge
- Non crea un collegamento dall’istanza a
Factory.prototype
— ma questa è in realtà una buona cosa perché non otterrai un ingannevoleinstanceof
. Invece,instanceof
fallirà sempre. Vedi benefici. -
this
non si riferisce al nuovo oggetto all’interno della fabbrica. Vedi benefici., - Può eseguire più lentamente di una funzione di costruttore nei benchmark di micro-ottimizzazione. Il percorso lento è ancora molto veloce-milioni di ops / sec su un vecchio computer. Questo è più probabile che sia un problema nel codice della libreria o del framework rispetto al codice dell’applicazione. Benchmark sempre dal punto di vista dell’utente prima di utilizzare micro-ottimizzazioni.
Conclusione
A mio parere,class
potrebbe avere una sintassi comoda, ma ciò non può compensare il fatto che attira gli utenti incauti a schiantarsi sulle rocce dell’ereditarietà della classe., È anche rischioso perché in futuro potresti voler eseguire l’aggiornamento a una factory, ma tutti i chiamanti saranno strettamente accoppiati alla funzione del costruttore a causa della parola chiave new
e il fatto che passare dalle classi alle fabbriche è un cambiamento di rottura.
Potresti pensare di poter semplicemente refactoring dei siti di chiamata, ma su team di grandi dimensioni, o se la classe con cui stai lavorando fa parte di un’API pubblica, potresti interrompere il codice che non è sotto il tuo controllo. In altre parole, non puoi sempre presumere che il refactoring dei chiamanti sia anche un’opzione.,
La cosa interessante delle fabbriche è che non sono solo più potenti e più flessibili, ma sono anche il modo più semplice per incoraggiare interi team e intere basi di utenti API a utilizzare modelli semplici, flessibili e sicuri.