Come viene chiamato questo pattern JavaScript e perché viene utilizzato?


100

Sto studiando THREE.js e ho notato un modello in cui le funzioni sono definite in questo modo:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(Esempio vedere il metodo raycast qui ).

La normale variazione di un tale metodo sarebbe simile a questa:

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

Confrontando la prima versione con la variante normale , la prima sembra differire in quanto:

  1. Assegna il risultato di una funzione autoeseguibile.
  2. Definisce una variabile locale all'interno di questa funzione.
  3. Restituisce la funzione effettiva contenente la logica che utilizza la variabile locale.

Quindi la differenza principale è che nella prima variazione la barra viene assegnata una sola volta, all'inizializzazione, mentre la seconda variazione crea questa variabile temporanea ogni volta che viene chiamata.

La mia ipotesi migliore sul motivo per cui viene utilizzato è che limita il numero di istanze per bar (ce ne sarà solo una) e quindi risparmia il sovraccarico di gestione della memoria.

Le mie domande:

  1. Questa ipotesi è corretta?
  2. C'è un nome per questo modello?
  3. Perché viene utilizzato?

1
@ ChrisHayes abbastanza giusto. L'ho etichettato come THREE.js perché pensavo che i contributori di THREE.js fossero i più qualificati per rispondere a questa domanda, ma sì, è una domanda JS generica.
Patrick Klug,

2
Credo che si chiamino chiusure. Puoi leggere su di loro.
StackFlowed

1
Se questo è l'unico punto in cui viene istanziato un Bar, allora è un pattern singleton .
Paul,

8
Non necessariamente per risparmiare memoria, ma può mantenere lo stato tra le invocazioni
Juan Mendes,

2
@wrongAnswer: non esattamente. qui la funzione anonima (che sarebbe la chiusura) viene eseguita immediatamente.
njzk2

Risposte:


100

Le tue ipotesi sono quasi corrette. Rivediamo prima quelli.

  1. Assegna il ritorno di una funzione autoeseguibile

Questa è chiamata espressione di funzione invocata immediatamente o IIFE

  1. Definisce una variabile locale all'interno di questa funzione

Questo è il modo di avere campi oggetto privati ​​in JavaScript poiché non fornisce privatealtrimenti la parola chiave o la funzionalità.

  1. Restituisce la funzione effettiva contenente la logica che utilizza la variabile locale.

Ancora una volta, il punto principale è che questa variabile locale è privata .

C'è un nome per questo modello?

Per quanto ne so, puoi chiamare questo pattern Module Pattern . Citando:

Il pattern Module incapsula "privacy", stato e organizzazione utilizzando le chiusure. Fornisce un modo per avvolgere un mix di metodi e variabili pubblici e privati, proteggendo i pezzi dalla fuoriuscita dell'ambito globale e dalla collisione accidentale con l'interfaccia di un altro sviluppatore. Con questo modello, viene restituita solo un'API pubblica, mantenendo privato tutto il resto all'interno della chiusura.

Confrontando questi due esempi, le mie migliori ipotesi sul motivo per cui viene utilizzato il primo sono:

  1. Sta implementando il modello di progettazione Singleton.
  2. Si può controllare il modo in cui un oggetto di un tipo specifico può essere creato usando il primo esempio. Una stretta corrispondenza con questo punto può essere rappresentata dai metodi di fabbrica statici descritti in Java efficace.
  3. È efficiente se hai bisogno dello stesso stato dell'oggetto ogni volta.

Ma se hai solo bisogno dell'oggetto vaniglia ogni volta, allora questo modello probabilmente non aggiungerà alcun valore.


1
+1 per aver identificato correttamente lo schema dal libro di Addy Osmani. Hai ragione nella tua denominazione - questo è davvero il modello del modulo - il modello del modulo rivelatore in questo tra l'altro.
Benjamin Gruenbaum

4
Sono d'accordo con la tua risposta, tranne la parte "nessuna variabile privata pronta per l'uso". Tutte le variabili JS hanno scope lessicale "out-of-the-box", che è un meccanismo più potente / generale rispetto alle variabili "private" (ad es. Come si trova in Java); quindi JS supporta le variabili "private" come un caso speciale del modo in cui gestisce tutte le variabili.
Warbo

Penso che per "variabili private" intendessi "campo oggetto" privato
Ian Ringrose,


4
@LukaHorvat: infatti javascript non è più "potente" di altri linguaggi (preferisco il termine espressivo). In realtà, è meno espressivo, poiché l'unico modo per proteggere le tue variabili è racchiuderle in funzione per evitare qualsiasi sciocchezza di riutilizzo delle variabili. Il pattern Module è un requisito definitivo per creare un buon codice javascript, ma non è una caratteristica del linguaggio, è più una triste soluzione alternativa per evitare di essere morsi dai punti deboli del linguaggio.
Falanwe

11

Limita i costi di inizializzazione degli oggetti e garantisce inoltre che tutte le chiamate di funzioni utilizzino lo stesso oggetto. Ciò consente, ad esempio, di memorizzare lo stato nell'oggetto per utilizzarlo in future chiamate.

Sebbene sia possibile che limiti l'utilizzo della memoria, in genere il GC raccoglierà comunque gli oggetti inutilizzati, quindi è probabile che questo modello non sia di grande aiuto.

Questo modello è una forma specifica di chiusura .


1
In JS di solito si riferisce come "modulo"
Lesha Ogonkov

2
Non la definirei "una forma specifica di chiusura", di per sé. È uno schema che utilizza una chiusura. Il nome del modello è ancora in palio.
Chris Hayes

4
Serve davvero un nome? Tutto deve essere uno schema? Abbiamo davvero bisogno di una tassonomia infinita di varianti del "modello di modulo"? Non può essere semplicemente "un IIFE con alcune variabili locali che restituisce una funzione?"
Dagg Nabbit

3
@DaggNabbit Quando la domanda è "come si chiama questo modello"? Sì, ha bisogno di un nome oppure di un argomento convincente che non ne ha uno. Inoltre, i modelli esistono per una ragione. Non capisco perché ti invevi contro di loro qui.
Chris Hayes,

4
@ChrisHayes se ha bisogno di un nome, perché dovresti sostenere che non ne ha uno? Non ha alcun senso. Se ne ha bisogno, non deve averne uno. Non ho problemi con i pattern, ma non credo sia necessario classificare ogni singolo linguaggio semplice come pattern. Ciò porta a pensare in modi limitati ("Questo è un modello di modulo? Sto usando correttamente il modello di modulo?" Vs. "Ho un IIFE con alcune variabili locali che restituisce una funzione, questo progetto funziona per me?")
Dagg Nabbit,

8

Non sono sicuro che questo modello abbia un nome più corretto, ma a me sembra un modulo e il motivo per cui viene utilizzato è sia per incapsulare che per mantenere lo stato.

La chiusura (identificata da una funzione all'interno di una funzione) garantisce che la funzione interna abbia accesso alle variabili all'interno della funzione esterna.

Nell'esempio che hai fornito, la funzione interna viene restituita (e assegnata a foo) eseguendo la funzione esterna, il che significa che tmpObjectcontinua a vivere all'interno della chiusura e più chiamate alla funzione interna foo()opereranno sulla stessa istanza di tmpObject.


5

La differenza fondamentale tra il tuo codice e il codice Three.js è che nel codice Three.js la variabile tmpObjectviene inizializzata solo una volta e quindi condivisa da ogni invocazione della funzione restituita.

Ciò sarebbe utile per mantenere uno stato tra le chiamate, simile a come le staticvariabili vengono utilizzate nei linguaggi C-like.

tmpObject è una variabile privata visibile solo alla funzione interna.

Cambia l'utilizzo della memoria, ma non è progettato per risparmiare memoria.


5

Vorrei contribuire a questo interessante thread estendendo il concetto del modello di modulo rivelatore, che garantisce che tutti i metodi e le variabili siano mantenuti privati ​​finché non vengono esplicitamente esposti.

inserisci qui la descrizione dell'immagine

In quest'ultimo caso, il metodo di addizione verrebbe chiamato Calculator.add ();


0

Nell'esempio fornito, il primo frammento userà la stessa istanza di tmpObject per ogni chiamata alla funzione foo (), dove come nel secondo frammento, tmpObject sarà una nuova istanza ogni volta.

Uno dei motivi per cui il primo snippet potrebbe essere stato utilizzato, è che la variabile tmpObject può essere condivisa tra le chiamate a foo (), senza che il suo valore venga trapelato nell'ambito in cui è dichiarato foo ().

La versione della funzione non eseguita immediatamente del primo frammento sarebbe effettivamente simile a questa:

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

Nota tuttavia che questa versione ha tmpObject nello stesso ambito di foo (), quindi potrebbe essere manipolata in seguito.

Un modo migliore per ottenere la stessa funzionalità sarebbe utilizzare un modulo separato:

Modulo "foo.js":

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

Modulo 2:

var foo = require('./foo');

Un confronto tra le prestazioni di un IEF e una funzione di creazione di foo denominata: http://jsperf.com/ief-vs-named-function


3
Il tuo esempio "migliore" funziona solo in NodeJS e non hai spiegato come sia migliore.
Benjamin Gruenbaum,

Creare un modulo separato non è "migliore", è solo diverso. In particolare, è un modo per comprimere le funzioni di ordine superiore fino a oggetti di primo ordine. Il codice del primo ordine tende ad essere più facile da seguire, ma è generalmente più dettagliato e ci obbliga a reificare i risultati intermedi.
Warbo

I moduli @BenjaminGruenbaum non sono solo in Node, ci sono molte soluzioni di moduli lato client, ad esempio browserify. Considero la soluzione del modulo "migliore" perché è più leggibile, più facile da eseguire il debug ed è più esplicita su cosa è nell'ambito e dove.
Kory Nunn
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.