Le chiusure sono spesso viste come un'arte arcana nella terra di JavaScript. Una volta masterizzati, ti permettono di scrivere un JavaScript davvero sorprendente. Questo articolo ti farà conoscere la magia delle chiusure di JavaScript.
Una delle verità chiave di JavaScript è quella qualunque cosa è un oggetto. Questo, ovviamente, include funzioni.
Una chiusura non è altro che un oggetto funzione con un ambito correlato in cui vengono risolte le variabili della funzione.
Le chiusure prendono il loro nome a causa del modo in cui loro vicino sui loro contenuti. Considera il seguente bit di JavaScript:
topping = "anchovi"; function pizzaParty (numSlices) var topping = "pepperoni", innerFunction = function () var topping = "ham"; console.log ("... Ma metti" + topping + "on" + numSlices + "slices"); ; console.log ("Questa pizza riguarda esclusivamente" + topping); innerFunction (); pizzaParty (3);
Se apri la tua console preferita ed esegui quel ragazzaccio, verrai accolto con un delizioso messaggio sull'effetto di "Questa pizza è tutta dedicata ai peperoni ... Ma metti il prosciutto su 3 fette". Questo esempio illustra alcuni concetti chiave di JavaScript cruciali per ottenere una sospensione delle chiusure.
Quanti oggetti funzione ci sono nel codice sopra? Bene ... abbiamo il nostro Pizzaparty
funzione e nidificata in quella funzione è innerFunction
. La matematica non è sempre stata la mia causa forte, ma 1 + 1 = 2
nel mio libro. Ogni oggetto funzione ha il proprio set di variabili, che sono risolte in ogni funzione scopo.
Le chiusure non possono essere pienamente comprese senza una solida base di applicazione. Il meccanismo di scope di JavaScript è ciò che consente a ciascuna funzione di avere il proprio guarnizione
variabile, e senza di esso potremmo avere troppi peperoni, troppo poco prosciutto, o * ansimi * ... alcune acciughe alla nostra pizza party. Usiamo una rapida illustrazione per illustrare meglio questa idea.
Le funzioni vengono eseguite utilizzando l'ambito che era in vigore al momento della definizione della funzione. Non ha nulla a che fare con lo scope in effetto quando viene chiamata la funzione.
Le frecce verdi mostrano che l'accessibilità funziona dall'esterno dentro. Le variabili definite nell'ambito al di fuori di una funzione sono accessibili dall'interno.
Se dovessimo omettere il guarnizione
variabile dall'interno del Pizzaparty
funzione, quindi avremmo ricevuto un messaggio del tipo "Questa pizza è tutta per gli anchovi", ma da allora Pizzaparty
ha un guarnizione
variabile all'interno del proprio ambito; quei ventosi salati non si avvicinano mai alla nostra festa della pizza.
Allo stesso modo, il numSlices
il parametro è accessibile dall'interno innerFunction
perché è definito nell'ambito sopra, in questo caso l'ambito di Pizzaparty
.
Le frecce rosse mostrano che le variabili nell'ambito di una funzione non sono mai accessibili al di fuori di quella funzione. Questo è il caso solo quando una variabile soddisfa una delle seguenti condizioni:
var
la parola chiave è in uso. Omettendo il var
parola chiave quando si imposta una variabile causerà JavaScript per impostare la variabile denominata più vicino nelle funzioni esterne fino all'ambito globale. Quindi, usando il nostro esempio, il prosciutto guarnizione
nel innerFunction
non è possibile accedere da Pizzaparty
, e i peperoni guarnizione
nel Pizzaparty
non è possibile accedere allo spazio globale in cui si trova l'anchovi.
Lo scope lessicale indica che le funzioni vengono eseguite utilizzando lo scope variabile in effetti quando la funzione era definito. Non ha nulla a che fare con lo scope in effetto quando viene chiamata la funzione. Questo fatto è cruciale per sbloccare il potere delle chiusure.
Ora che comprendiamo cos'è una chiusura e quale ambito significano le chiusure, analizziamo alcuni casi d'uso classici.
Le chiusure sono il modo per nascondere il tuo codice agli occhi del pubblico. Con chiusure, puoi facilmente avere membri privati che sono protetti dal mondo esterno:
(function (exports) function myPrivateMultiplyFunction (num, num2) return num * num2; // equivalente a window.multiply = function (num1, num2) ... exports.multiply = function (num1, num2) console.log (myPrivateMultiplyFunction (num1, num2));) (finestra);
Con chiusure, puoi facilmente avere membri privati che sono protetti dal mondo esterno.
Scopriamolo. Il nostro oggetto funzione di livello superiore è una funzione anonima:
(funzione (esportazioni) ) (finestra);
Invochiamo subito questa funzione anonima. Lo passiamo al contesto globale (finestra
in questo caso) in modo che possiamo "esportare" una funzione pubblica, ma nascondere tutto il resto. Perché la funzione myPrivateMultiplyFunction
è una funzione annidata, esiste solo nell'ambito della nostra chiusura; quindi possiamo usarlo ovunque all'interno di questo ambito e solo in questo ambito.
JavaScript terrà un riferimento alla nostra funzione privata da utilizzare all'interno della funzione di moltiplicazione, ma myPrivateMultiplyFunction
non è possibile accedere al di fuori della chiusura. Proviamo questo:
multiply (2,6) // => 12 myPrivateMultiplyFunction (2,6) // => ReferenceError: myPrivateMultiplyFunction non è definito
La chiusura ci ha permesso di definire una funzione per uso privato, pur continuando a consentirci di controllare ciò che il resto del mondo vede. Cos'altro possono fare le chiusure?
Le chiusure sono abbastanza utili quando si tratta di generare codice. Stanco di ricordare tutti quei fastidiosi codici chiave per gli eventi della tastiera? Una tecnica comune è usare una mappa chiave:
var KeyMap = "Enter": 13, "Shift": 16, "Tab": 9, "LeftArrow": 37;
Quindi, nel nostro evento tastiera, vogliamo verificare se è stato premuto un determinato tasto:
var txtInput = document.getElementById ('myTextInput'); txtInput.onkeypress = function (e) var code = e.keyCode || e.which // tariffa normale per ottenere il tasto premuto if (code === KeyMap.Enter) console.log (txtInput.value);
L'esempio sopra riportato non è il peggiore, ma possiamo usare meta-programmazione e chiusure per creare una soluzione ancora migliore. Usando il nostro esistente kEYMAP
oggetto, possiamo generare alcune funzioni utili:
per (chiave var in KeyMap) // accesso oggetto con accessor di matrice per impostare il nome della funzione "dyanamic" KeyMap ["is" + key] = (funzione (confronta) return function (ev) var code = ev.keyCode | | ev.which; codice di ritorno === compare;) (KeyMap [chiave]);
Le chiusure sono così potenti perché possono catturare la variabile locale e i collegamenti dei parametri della funzione in cui sono definiti.
Questo ciclo genera un è
funzione per ogni chiave in kEYMAP
, e il nostro txtInput.onkeypress
la funzione diventa un po 'più leggibile:
var txtInput = document.getElementById ('myTextInput'); txtInput.onkeypress = function (e) if (KeyMap.isEnter (e)) console.log (txtInput.value);
La magia inizia qui:
KeyMap ["è" + chiave] = (funzione (confronta) ) (KeyMap [tasto]); // invoca immediatamente e passa il valore corrente in KeyMap [tasto]
Mentre aggiriamo le chiavi kEYMAP
, passiamo il valore referenziato da quella chiave alla funzione esterna anonima e lo invochiamo immediatamente. Questo lega quel valore al confrontare
parametro di questa funzione.
La chiusura a cui siamo interessati è quella che stiamo ritornando dall'interno della funzione anonima:
return function (ev) var code = ev.keyCode || ev.which; codice di ritorno === confronta;
Ricorda, le funzioni sono eseguite con lo scope che era al loro posto quando sono state definite. Il confrontare
parametro è legato al kEYMAP
valore che era presente durante l'iterazione del ciclo, e quindi la nostra chiusura annidata è in grado di catturarla. Scattiamo un'istantanea nel tempo dello scope che era in un effetto in quel momento.
Le funzioni che abbiamo creato ci permettono di saltare la configurazione del codice
variabile ogni volta che vogliamo controllare il codice chiave, e ora abbiamo funzioni comode e leggibili da usare.
A questo punto, dovrebbe essere relativamente facile vedere che le chiusure sono vitali per scrivere JavaScript di prima qualità. Applichiamo ciò che sappiamo sulle chiusure per aumentare uno dei tipi nativi di JavaScript (gasp!). Concentrandoci sugli oggetti funzione, aumentiamo il nativo Funzione
genere:
Function.prototype.cached = function () var self = this, // "this" fa riferimento alla funzione originale cache = ; // la nostra cache locale restituisce la funzione di restituzione dello storage cache (args) if (args in cache) restituisce la cache [args]; return cache [args] = self (args); ; ;
Questa piccola gemma consente a qualsiasi funzione di creare una versione cache di se stessa. Puoi vedere che la funzione restituisce una funzione stessa, quindi questo miglioramento può essere applicato e utilizzato in questo modo:
Math.sin = Math.sin.cached (); Math.sin (1) // => 0.8414709848078965 Math.sin (1) // => 0.8414709848078965 questa volta estratto dalla cache
Notare le abilità di chiusura che entrano in gioco. Abbiamo un locale nascondiglio
variabile che viene mantenuta privata e protetta dal mondo esterno. Ciò impedirà qualsiasi manomissione che potrebbe invalidare la nostra cache.
La chiusura restituita ha accesso ai collegamenti della funzione esterna, e ciò significa che siamo in grado di restituire una funzione con accesso completo alla cache all'interno, oltre alla funzione originale! Questa piccola funzione può fare meraviglie per le prestazioni. Questa particolare estensione è impostata per gestire un argomento, ma mi piacerebbe vedere la tua pugnalata a una funzione cache a più argomenti.
Come bonus aggiuntivo, diamo un'occhiata a un paio di usi di chiusure in natura.
A volte, il famoso jQuery $
factory non è disponibile (si pensi a WordPress), e noi vogliamo usarlo nel modo in cui lo facciamo tipicamente. Piuttosto che raggiungere jQuery.noConflict
, possiamo usare una chiusura per consentire alle funzioni interne di accedere al nostro $
legame ai parametri.
(function ($) $ (document) .ready (function () // business as usual ...);) (jQuery);
Su progetti Backbone.js di grandi dimensioni, potrebbe essere preferibile avere i propri modelli di applicazioni privati e quindi esporre un'API pubblica nella visualizzazione principale dell'applicazione. Usando una chiusura, puoi facilmente ottenere questa privacy.
(function (exports) var Product = Backbone.Model.extend (urlRoot: '/ products',); var ProductList = Backbone.Collection.extend (url: '/ products', model: Product); var Products = new ProductList; var ShoppingCartView = Backbone.View.extend (addProduct: function (prodotto, opts) return CartItems.create (product, opts);, removeProduct: function (product, opts) Products.remove (prodotto , opts);, getProduct: function (productId) return Products.get (productId);, getProducts: function () return Products.models;); // esporta solo la vista dell'applicazione principale exports.ShoppingCart = nuova ShoppingCartView;) (finestra);
Un breve riepilogo di ciò che abbiamo imparato:
Grazie mille per aver letto! Sentiti libero di fare qualsiasi domanda. Ora godiamoci la festa della pizza!