Lukker Front to Back

Lukker er ofte sett på som en arkanisk kunst i JavaScript-området. Når du har mestret, lar de deg skrive noen virkelig fantastisk JavaScript. Denne artikkelen vil gi deg fart på den magiske JavaScript-nedleggelsen.


Hva er en lukning?

En av de viktigste sannhetene til JavaScript er det alt er et objekt. Dette inkluderer selvsagt funksjoner.

En lukking er ikke noe mer enn et funksjonsobjekt med et tilhørende omfang der funksjonens variabler er løst.

Lukker får navnet sitt på grunn av måten de har Lukk over innholdet. Vurder følgende bit av JavaScript:

 topping = "anchovi"; funksjon pizzaParty (numSlices) var topping = "pepperoni", innerFunksjon = funksjon () var topping = "skinke"; console.log ("... men sett" + topping + "on" + numSlices + "skiver"); ; console.log ("Denne pizza handler om" + topping); innerFunction ();  pizzaParty (3);

Hvis du åpner din favorittkonsoll og kjører den dårlige gutten, blir du møtt med en deilig melding om effekten av "Denne pizza handler om pepperoni ... Men legg ham på 3 skiver." Dette eksemplet illustrerer noen viktige begreper for JavaScript som er avgjørende for å få tak i nedleggelser.

En lukning er et funksjonsobjekt

Hvor mange funksjonsobjekter er i koden ovenfor? Vel ... vi har vår pizzaParty funksjon og nestet i den funksjonen er innerFunction. Math har ikke alltid vært min sterke drakt, men 1 + 1 = 2 i boken min. Hver funksjon objekt har sitt eget sett med variabler, som er løst i hver funksjon omfang.

En lukking har sitt eget omfang

Lukker kan ikke forstås fullt ut uten en fast jording i omfang. JavaScript-mekanismen er det som tillater hver funksjon å ha sin egen topping variabel, og uten det kan vi ha for mye pepperoni, for lite skinke, eller * hakke * ... noen ansjoser på vårt pizza parti. La oss bruke en rask illustrasjon for å bedre illustrere denne ideen.

Funksjonene utføres ved hjelp av omfanget som var i kraft når funksjonen ble definert. Det har ingenting å gjøre med omfanget i kraft når funksjonen kalles.

Variabel Tilgjengelighet Fungerer utenfor-In

De grønne pilene viser at tilgjengeligheten virker fra utsiden. Variabler som er definert i omfanget utenfor en funksjon, er tilgjengelig fra det.

Hvis vi skulle utelate topping variabel fra innsiden av pizzaParty funksjon, så vil vi få en melding som "Denne pizza handler om ansjos", men siden pizzaParty har en topping variabel innenfor eget omfang de salte sugene kommer aldri til å komme i nærheten av pizzafestet vårt.

På samme måte, numSlices parameteren er tilgjengelig fra innsiden innerFunction fordi det er definert i omfanget ovenfor - i dette tilfellet omfanget av pizzaParty.

Variabel tilgjengelighet virker ikke inne-ute

De røde pilene viser at variabler i omfanget for en funksjon aldri er tilgjengelige utenfor den funksjonen. Dette er bare tilfellet når en variabel oppfyller ett av følgende forhold:

  1. De Var søkeord blir brukt.
  2. Variabelen er en parameter til funksjonen eller en ytre funksjon.
  3. Variabelen er en nestet funksjon.

Utelatelse av Var Søkeord når du angir en variabel, får JavaScript til å sette den nærmeste navngitte variabelen i ytre funksjoner helt opp til det globale omfanget. Så, ved hjelp av vårt eksempel, skinke topping i innerFunction kan ikke nås fra pizzaParty, og pepperoni topping i pizzaParty kan ikke nås i det globale omfanget hvor ansjos bor.

JavaScript bruker Lexical Scoping

Lexisk omfang betyr at funksjoner utføres ved å bruke variabel omfanget når funksjonen var definert. Det har ingenting å gjøre med omfanget i kraft når funksjonen kalles. Dette faktum er avgjørende for å låse opp lukkekraften.

Nå som vi forstår hva en nedleggelse er, og hvilket omfang betyr for nedleggelser, la oss dykke inn i noen klassiske brukstilfeller.


Bruk av lukninger for personvern

Lukker er de måte å skjule koden din fra det offentlige øye. Med nedleggelser kan du enkelt ha private medlemmer som er skjermet fra omverdenen:

 (funksjon (eksport) funksjon myPrivateMultiplyFunction (num, num2) return num * num2; // ekvivalent med window.multiply = funksjon (num1, num2) ... export.multiply = funksjon (num1, num2) console.log (myPrivateMultiplyFunction (num1, num2));) (vindu);

Med nedleggelser kan du enkelt ha private medlemmer som er skjermet fra omverdenen.

La oss slå det ned. Vårt øverste nivåfunksjonsobjekt er en anonym funksjon:

 (funksjon (eksport) ) (vindu);

Vi anvender denne anonyme funksjonen med en gang. Vi sender den den globale konteksten (vindu i dette tilfellet) slik at vi kan "eksportere" en offentlig funksjon, men gjemmer alt annet. Fordi funksjonen myPrivateMultiplyFunction er en nestet funksjon, eksisterer den bare innenfor omfanget av vår nedleggelse; slik at vi kan bruke det hvor som helst innenfor dette omfanget, og bare i dette omfanget.

JavaScript vil holde en referanse til vår private funksjon for bruk inne i multipliseringsfunksjonen, men myPrivateMultiplyFunction kan ikke nås utenfor lukkingen. La oss prøve dette ut:

 multiplisere (2,6) // => 12 myPrivateMultiplyFunction (2,6) // => ReferenceError: myPrivateMultiplyFunction er ikke definert

Lukkingen har gitt oss mulighet til å definere en funksjon for privat bruk, samtidig som vi tillater oss å kontrollere hva resten av verden ser. Hva annet kan lukkene gjøre?


Bruke Lukker For Meta-Programmering

Lukking er ganske nyttig når det gjelder generering av kode. Er du lei av å huske alle de irriterende nøkkelkoder for tastaturhendelser? En vanlig teknikk er å bruke et nøkkelkort:

 var KeyMap = "Enter": 13, "Shift": 16, "Tab": 9, "LeftArrow": 37;

Deretter vil vi i vår tastaturhendelse kontrollere om en bestemt nøkkel ble trykket:

 var txtInput = document.getElementById ('myTextInput'); txtInput.onkeypress = funksjon (e) var kode = e.keyCode || e.which // vanlig fare for å få den trykte tasten hvis (kode === KeyMap.Enter) console.log (txtInput.value); 

Fange et øyeblikk i tid

Eksemplet ovenfor er ikke det verste, men vi kan bruke meta-programmering og nedleggelser for å gi en enda bedre løsning. Bruke vår eksisterende tastaturoppsett objekt, vi kan generere noen nyttige funksjoner:

 for (var nøkkel i KeyMap) // tilgang objekt med array accessor for å sette "dyanamic" funksjonsnavn KeyMap ["er" + nøkkel] = (funksjon (sammenligne) return funksjon (ev) var code = ev.keyCode | | ev.which; returkode === sammenligne;) (KeyMap [key]); 

Lukker er så kraftige fordi de kan fange den lokale variabelen og parameterbindingene av funksjonen de er definert i.

Denne sløyfen genererer en er funksjon for hver nøkkel inn tastaturoppsett, og vår txtInput.onkeypress funksjonen blir litt mer lesbar:

 var txtInput = document.getElementById ('myTextInput'); txtInput.onkeypress = funksjon (e) if (KeyMap.isEnter (e)) console.log (txtInput.value); 

Den magiske starter her:

 KeyMap ["er" + nøkkel] = (funksjon (sammenligne) ) (KeyMap [key]); // påkalle umiddelbart og sende nåværende verdi ved KeyMap [key]

Som vi løp over tastene i tastaturoppsett, Vi overfører verdien som refereres til av nøkkelen til den anonyme ytre funksjonen og påkaller den umiddelbart. Dette binder den verdien til sammenligne parameter for denne funksjonen.

Lukkingen vi er interessert i er den vi returnerer fra den anonyme funksjonen:

 returfunksjon (ev) var kode = ev.keyCode || ev.which; returkode === sammenligne; 

Husk, funksjoner utføres med omfanget som var på plass da de ble definert. De sammenligne parameteren er bundet til tastaturoppsett verdien som var på plass under en loop-iterasjon, og så er vår nestede lukking i stand til å fange den. Vi tar et øyeblikksbilde i tid av omfanget som var i effekt i det øyeblikket.

Funksjonene vi opprettet tillater oss å hoppe over å sette opp kode variabel hver gang vi vil sjekke nøkkelkoden, og vi har nå praktiske, lesbare funksjoner å bruke.


Bruk av lukninger for å forlenge språket

På dette tidspunktet bør det være relativt enkelt å se at nedleggelser er avgjørende for å skrive topp notch JavaScript. La oss bruke det vi vet om nedleggelser for å utvide en av JavaScript-opprinnelige typer (gisp!). Med vårt fokus på funksjonsobjekter, la oss forstørre den innfødte Funksjon type:

 Function.prototype.cached = function () var self = this, // "this" refererer til den opprinnelige funksjonen cache = ; // vår lokale, leksisk scoped cache lagring returneringsfunksjon (args) hvis (args i cache) returnere cache [args]; returnere cache [args] = self (args); ; ;

Denne lille perlen tillater enhver funksjon å lage en bufret versjon av seg selv. Du kan se at funksjonen returnerer en funksjon selv, slik at denne forbedringen kan brukes og brukes slik:

 Math.sin = Math.sin.cached (); Math.sin (1) // => 0.8414709848078965 Math.sin (1) // => 0.8414709848078965 denne gangen hentet fra cache

Legg merke til lukkekompetanse som kommer inn i spill. Vi har en lokal cache variabel som holdes privat og skjermet fra omverdenen. Dette forhindrer manipulering som kan føre til at cachen blir ugyldig.

Lukkingen som returneres har tilgang til den ytre funksjonens bindinger, og det betyr at vi kan returnere en funksjon med full tilgang til hurtigbufferen inni, så vel som den opprinnelige funksjonen! Denne lille funksjonen kan gjøre underverker for ytelse. Denne spesielle utvidelsen er satt opp for å håndtere ett argument, men jeg vil gjerne se staben din ved en cache-funksjon med flere argumenter.


Closures in the Wild

Som en ekstra bonus, la oss ta en titt på et par bruksområder av lukkinger i naturen.

jQuery

Noen ganger er den berømte jQuery $ fabrikken er ikke tilgjengelig (tenk WordPress), og vi vil bruke den på den måten vi vanligvis gjør. Snarere enn å nå for jQuery.noConflict, Vi kan bruke en nedleggelse for å tillate funksjoner inne for å få tilgang til vårt $ parameterbinding.

 (funksjon ($) $ (dokument) .ready (funksjon () // virksomhet som vanlig ...);) (jQuery);

Backbone.js

På store Backbone.js-prosjekter kan det være gunstig å ha applikasjonsmodellene dine private, og avslør deretter en offentlig API på hovedapplikasjonsvisningen. Ved hjelp av en nedleggelse kan du enkelt oppnå dette personvernet.

 (funksjon (eksport) var Produkt = Backbone.Model.extend (urlRoot: '/ products',); var ProductList = Ryggrad.Kollection.extend (url: '/ products', modell: Product); var Products = new ProductList; var ShoppingCartView = Ryggrad.View.extend (addProduct: function (product, ops) return CartItems.create (product, ops);, removeProduct: function (product, ops) Products.remove , opptast);, getProdukt: funksjon (productId) return Products.get (productId);, getProducts: function () return Products.models;; // eksporter hovedprogrammets visning bare exports.ShoppingCart = ny ShoppingCartView;) (vindu);

Konklusjon

En rask omtale av det vi lærte:

  • En lukning er ikke noe mer enn et funksjonsobjekt med et omfang.
  • Lukker får navnet sitt på den måten de "lukker" over innholdet.
  • Lukker kontanter i stor tid på JavaScript's leksikalske omfang.
  • Lukk er veien for å oppnå privatliv i JavaScript.
  • Lukker er i stand til å fange den lokale variabelen og parameterbinding av en ytre funksjon.
  • JavaScript kan bli kraftig utvidet med noen lukke magi.
  • Lukker kan brukes sammen med mange av favorittbibliotekene dine for å gjøre dem enda kjøligere!

Takk så mye for å lese! Ta gjerne spørsmål. La oss nå nyte pizzafestet!