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.
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.
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.
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.
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
.
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:
Var
søkeord blir brukt. 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.
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.
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?
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);
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.
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.
Som en ekstra bonus, la oss ta en titt på et par bruksområder av lukkinger i naturen.
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);
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);
En rask omtale av det vi lærte:
Takk så mye for å lese! Ta gjerne spørsmål. La oss nå nyte pizzafestet!