Chiusure in JavaScript spiegate con esempi

Cosa sono le chiusure?

Una chiusura è la combinazione di una funzione e dell'ambiente lessicale (ambito) all'interno del quale quella funzione è stata dichiarata. Le chiusure sono una proprietà fondamentale e potente di Javascript. Questo articolo discute il "come" e il "perché" delle chiusure:

Esempio

//we have an outer function named walk and an inner function named fly function walk (){ var dist = '1780 feet'; function fly(){ console.log('At '+dist); } return fly; } var flyFunc = walk(); //calling walk returns the fly function which is being assigned to flyFunc //you would expect that once the walk function above is run //you would think that JavaScript has gotten rid of the 'dist' var flyFunc(); //Logs out 'At 1780 feet' //but you still can use the function as above //this is the power of closures

Un altro esempio

function by(propName) { return function(a, b) { return a[propName] - b[propName]; } } const person1 = {name: 'joe', height: 72}; const person2 = {name: 'rob', height: 70}; const person3 = {name: 'nicholas', height: 66}; const arr_ = [person1, person2, person3]; const arr_sorted = arr_.sort(by('height')); // [ { name: 'nicholas', height: 66 }, { name: 'rob', height: 70 },{ name: 'joe', height: 72 } ]

La chiusura 'ricorda' l'ambiente in cui è stata creata. Questo ambiente è costituito da tutte le variabili locali che erano nell'ambito al momento della creazione della chiusura.

function outside(num) { var rememberedVar = num; // In this example, rememberedVar is the lexical environment that the closure 'remembers' return function inside() { // This is the function which the closure 'remembers' console.log(rememberedVar) } } var remember1 = outside(7); // remember1 is now a closure which contains rememberedVar = 7 in its lexical environment, and //the function 'inside' var remember2 = outside(9); // remember2 is now a closure which contains rememberedVar = 9 in its lexical environment, and //the function 'inside' remember1(); // This now executes the function 'inside' which console.logs(rememberedVar) => 7 remember2(); // This now executes the function 'inside' which console.logs(rememberedVar) => 9 

Le chiusure sono utili perché consentono di "ricordare" i dati e quindi di operare su tali dati tramite le funzioni restituite. Ciò consente a javascript di emulare metodi privati ​​che si trovano in altri linguaggi di programmazione. I metodi privati ​​sono utili per limitare l'accesso al codice e per gestire lo spazio dei nomi globale.

Variabili private e metodi

Le chiusure possono essere utilizzate anche per incapsulare dati / metodi privati. Dai un'occhiata a questo esempio:

const bankAccount = (initialBalance) => { const balance = initialBalance; return { getBalance: function() { return balance; }, deposit: function(amount) { balance += amount; return balance; }, }; }; const account = bankAccount(100); account.getBalance(); // 100 account.deposit(10); // 110

In questo esempio, non saremo in grado di accedere balanceda nessuna parte al di fuori della bankAccountfunzione, il che significa che abbiamo appena creato una variabile privata. Dov'è la chiusura? Bene, pensa a cosa bankAccount()sta tornando. In realtà restituisce un oggetto con un mucchio di funzioni al suo interno, e tuttavia quando lo chiamiamo account.getBalance(), la funzione è in grado di "ricordare" il suo riferimento iniziale a balance. Questo è il potere della chiusura, dove una funzione "ricorda" il suo ambito lessicale (ambito del tempo di compilazione), anche quando la funzione viene eseguita al di fuori di tale ambito lessicale.

Emulazione di variabili con ambito di blocco.

Javascript non prevedeva il concetto di variabili con ambito a blocchi. Significa che quando si definisce una variabile all'interno di un forloop, ad esempio, questa variabile è visibile anche dall'esterno del forloop. Allora come possono le chiusure aiutarci a risolvere questo problema? Diamo un'occhiata.

 var funcs = []; for(var i = 0; i < 3; i++){ funcs[i] = function(){ console.log('My value is ' + i); //creating three different functions with different param values. } } for(var j = 0; j < 3; j++){ funcs[j](); // My value is 3 // My value is 3 // My value is 3 }

Poiché la variabile i non ha un ambito di blocco, il suo valore all'interno di tutte e tre le funzioni è stato aggiornato con il contatore di loop e ha creato valori dannosi. La chiusura può aiutarci a risolvere questo problema creando un'istantanea dell'ambiente in cui si trovava la funzione quando è stata creata, preservandone lo stato.

 var funcs = []; var createFunction = function(val){ return function() {console.log("My value: " + val);}; } for (var i = 0; i < 3; i++) { funcs[i] = createFunction(i); } for (var j = 0; j < 3; j++) { funcs[j](); // My value is 0 // My value is 1 // My value is 2 }

Le ultime versioni di javascript es6 + hanno una nuova parola chiave chiamata let che può essere usata per dare alla variabile un blockscope. Ci sono anche molte funzioni (forEach) e intere librerie (lodash.js) dedicate alla risoluzione di problemi come quelli spiegati sopra. Possono certamente aumentare la tua produttività, tuttavia rimane estremamente importante conoscere tutti questi problemi quando si tenta di creare qualcosa di grande.

Le chiusure hanno molte applicazioni speciali utili quando si creano grandi programmi javascript.

  1. Emulazione di variabili private o incapsulamento
  2. Effettuare chiamate lato server asincrone
  3. Creazione di una variabile con ambito di blocco.

Emulazione di variabili private.

A differenza di molti altri linguaggi, Javascript non ha un meccanismo che consente di creare variabili di istanza incapsulate all'interno di un oggetto. La presenza di variabili di istanza pubbliche può causare molti problemi durante la creazione di programmi di dimensioni medio-grandi. Tuttavia, con le chiusure, questo problema può essere mitigato.

Proprio come nell'esempio precedente, puoi costruire funzioni che restituiscono letterali oggetto con metodi che hanno accesso alle variabili locali dell'oggetto senza esporle. Quindi, rendendoli effettivamente privati.

Le chiusure possono anche aiutarti a gestire il tuo spazio dei nomi globale per evitare collisioni con dati condivisi a livello globale. Di solito tutte le variabili globali sono condivise tra tutti gli script del tuo progetto, il che ti darà sicuramente molti problemi quando crei programmi medio-grandi. Questo è il motivo per cui gli autori di librerie e moduli usano le chiusure per nascondere i metodi ei dati di un intero modulo. Questo è chiamato il modello del modulo, utilizza un'espressione di funzione immediatamente invocata che esporta solo alcune funzionalità nel mondo esterno, riducendo significativamente la quantità di riferimenti globali.

Ecco un breve esempio dello scheletro di un modulo.

var myModule = (function() = { let privateVariable = 'I am a private variable'; let method1 = function(){ console.log('I am method 1'); }; let method2 = function(){ console.log('I am method 2, ', privateVariable); }; return { method1: method1, method2: method2 } }()); myModule.method1(); // I am method 1 myModule.method2(); // I am method 2, I am a private variable

Le chiusure sono utili per acquisire nuove istanze di variabili private contenute nell'ambiente "ricordato" e a tali variabili è possibile accedere solo tramite la funzione oi metodi restituiti.

Vettori

Un vettore è forse il tipo più semplice di raccolta in Clojure. Puoi pensarlo come un array in Javascript. Definiamo un vettore semplice:

(def a-vector [1 2 3 4 5]) ;; Alternatively, use the vector function: (def another-vector (vector 1 2 3 4 5)) ;; You can use commas to separate items, since Clojure treats them as whitespace. (def comma-vector [1, 2, 3, 4, 5])

Vedrai che utilizza parentesi quadre, proprio come un array in JS. Poiché Clojure, come JS, è tipizzato dinamicamente, i vettori possono contenere elementi di qualsiasi tipo, inclusi altri vettori.

(def mixed-type-vector [1 "foo" :bar ["spam" 22] #"^baz$"])

Aggiunta di elementi a un vettore

Puoi aggiungere elementi a un vettore usando conj. Puoi anche anteporre a un elenco usando into, ma nota che intoè inteso per unire due vettori, quindi entrambi i suoi argomenti devono essere vettori e l'uso intoè più lento dell'uso conj.

(time (conj [1 2] 3)) ; => "Elapsed time: 0.032206 msecs" ; [1 2 3] (time (into [1] [2 3])) ; => "Elapsed time: 0.078499 msecs" ; [1 2 3]
:rocket:

IDEOne it!

Recupero di elementi da un vettore

You can retrieve items from a vector using get. This is equivalent to using bracket notation to access items in an array in many imperative languages. Items in a vector are 0-indexed, counting from the left.

var arr = [1, 2, 3, 4, 5]; arr[0]; // => 1

In Clojure, this would be written like so:

(def a-vector [1 2 3 4 5]) (get a-vector 0) ; => 1

You can also give get a default value, if you give it an index that isn’t in the array.

;; the list doesn't have 2147483647 elements, so it'll return a string instead. (get a-vector 2147483646 "sorry, not found!") ; => "sorry, not found!"

Converting other collections into vectors

Non-vector data structures can be converted into vectors using the vec function. With hashmaps, this produces a 2D vector containing pairs of keys and values.

(vec '(1 2 3 4 5)) ; => [1 2 3 4 5] (vec {:jack "black" :barry "white"}) ; => [[:jack "black"] [:barry "white"]]

When to use a vector?

A vector should be used in almost all cases if you need a collection, because they have the shortest random-access times, which makes it easy to retrieve items from a vector. Note that vectors are ordered. If order doesn’t matter, it may be better to use a set. Also note that vectors are designed for appending items; if you need to prepend items, you might want to use a list.

More info on Closures:

  • Learn JavaScript closures in six minutes
  • A basic guide to closures in JavaScript
  • Discover the power of closures in VueJS
  • JavaScript closures explained by mailing a package