Een eenvoudige uitleg van JavaScript Closures

De callbacks, event handlers, hogere-orde functies kunnen toegang krijgen tot outer scope variabelen dankzij closures. Closures zijn belangrijk in functioneel programmeren, en worden vaak gevraagd tijdens het JavaScript coderingsinterview.

Hoewel closures overal worden gebruikt, zijn ze moeilijk te begrijpen. Als je je “Aha!”-moment in het begrijpen van closures nog niet hebt gehad, dan is deze post voor jou.

Ik zal beginnen met de fundamentele termen: scope en lexical scope. Daarna, nadat je de basis hebt begrepen, heb je nog maar één stap nodig om closures eindelijk te begrijpen.

Voordat je begint, stel ik voor dat je de neiging weerstaat om de secties over scope en lexical scope over te slaan. Deze concepten zijn cruciaal voor closures, en als je ze goed begrijpt, wordt het idee van closure vanzelfsprekend.

1. De scope

Als je een variabele definieert, wil je dat die binnen bepaalde grenzen toegankelijk is. Bv. een result variabele is logisch om te bestaan binnen een calculate() functie, als een intern detail. Buiten de calculate(), is de result variabele nutteloos.

De toegankelijkheid van variabelen wordt beheerd door scope. Je bent vrij om de variabele te benaderen die binnen zijn bereik is gedefinieerd. Maar buiten dat bereik is de variabele ontoegankelijk.

In JavaScript wordt een bereik gemaakt door een functie of codeblok.

Laten we eens kijken hoe het bereik de beschikbaarheid van een variabele beïnvloedt count. Deze variabele behoort tot een bereik dat is gemaakt door de functie foo():

function foo() { // The function scope let count = 0; console.log(count); // logs 0}foo();console.log(count); // ReferenceError: count is not defined

count is vrij toegankelijk binnen het bereik van foo().

Buiten de foo() scope is count echter niet toegankelijk. Als u probeert count van buitenaf toch te benaderen, gooit JavaScript ReferenceError: count is not defined.

In JavaScript zegt het bereik: als je een variabele hebt gedefinieerd binnen een functie of codeblok, dan kun je deze variabele alleen binnen die functie of dat codeblok gebruiken. Het bovenstaande voorbeeld demonstreert dit gedrag.

De JavaScript-werkingssfeer

Nu een algemene formulering:

De reikwijdte is een ruimtebeleid dat de toegankelijkheid van variabelen regelt.

Een onmiddellijke eigenschap doet zich voor: de scope isoleert variabelen. Dat is mooi, want verschillende scopes kunnen variabelen met dezelfde naam hebben.

Je kunt gemeenschappelijke variabelennamen (countindexcurrentvalue, etc) in verschillende scopes hergebruiken zonder dat er botsingen optreden.

foo() en bar() functie scopes hebben hun eigen, maar dezelfde naam, variabelen count:

function foo() { // "foo" function scope let count = 0; console.log(count); // logs 0}function bar() { // "bar" function scope let count = 1; console.log(count); // logs 1}foo();bar();

count variabelen uit foo() en bar() functiescopes botsen niet.

2. Scopes nesting

Laten we nog wat meer spelen met scopes, en de ene scope in de andere zetten.

De functie innerFunc() is genest binnen een buitenste functie outerFunc().

De JavaScript scopes kunnen genest worden

Hoe zouden de 2 functie scopes met elkaar interageren? Kan ik de variabele outerVar van outerFunc() benaderen vanuit de innerFunc() scope?

Laten we dat eens proberen in het voorbeeld:

function outerFunc() { // the outer scope let outerVar = 'I am outside!'; function innerFunc() { // the inner scope console.log(outerVar); // => logs "I am outside!" } innerFunc();}outerFunc();

Inderdaad, outerVar variabele is toegankelijk binnen innerFunc() scope. De variabelen van het buitenste bereik zijn toegankelijk binnen het binnenste bereik.

Nu weet je 2 interessante dingen:

  • Scopes kunnen genest worden
  • De variabelen van de outer scope zijn toegankelijk binnen de inner scope

3. De lexicale scope

Hoe begrijpt JavaScript dat outerVar binnen innerFunc() correspondeert met de variabele outerVar van outerFunc()?

Dat komt omdat JavaScript een scoping-mechanisme implementeert dat lexical scoping (of static scoping) wordt genoemd. Lexical scoping betekent dat de toegankelijkheid van variabelen wordt bepaald door de positie van de variabelen in de broncode binnen de nesting scopes.

Eenvoudiger gezegd, lexical scoping betekent dat je binnen de binnenste scope toegang hebt tot variabelen van de buitenste scopes.

Het wordt lexisch (of statisch) genoemd omdat de engine (op het moment van lexing) de nesting van scopes bepaalt door alleen maar naar de JavaScript broncode te kijken, zonder deze uit te voeren.

Hier ziet u hoe de engine het vorige stukje code begrijpt:

  1. Ik zie dat u een functie definieert outerFunc() die een variabele heeft outerVar. Goed.
  2. Binnen de outerFunc(), zie ik dat je een functie innerFunc() definieert.
  3. Binnen de innerFunc(), zie ik een variabele outerVar zonder declaratie. Omdat ik lexical scoping gebruik, beschouw ik de variabele outerVar binnen innerFunc() als dezelfde variabele als outerVar van outerFunc().

Het gedistilleerde idee van de lexical scope:

De lexical scope bestaat uit buitenste scopes die statisch worden bepaald.

Voorbeeld:

const myGlobal = 0;function func() { const myVar = 1; console.log(myGlobal); // logs "0" function innerOfFunc() { const myInnerVar = 2; console.log(myVar, myGlobal); // logs "1 0" function innerOfInnerOfFunc() { console.log(myInnerVar, myVar, myGlobal); // logs "2 1 0" } innerOfInnerOfFunc(); } innerOfFunc();}func();

De lexicale scope van innerOfInnerOfFunc() bestaat uit scopes van innerOfFunc()func() en globale scope (de buitenste scope). Binnen innerOfInnerOfFunc() heeft u toegang tot de lexical scope variabelen myInnerVarmyVar en myGlobal.

Het lexicale bereik van innerFunc() bestaat uit func() en globaal bereik. Binnen innerOfFunc() kunt u de lexical scope variabelen myVar en myGlobal benaderen.

Ten slotte bestaat de lexical scope van func() alleen uit de global scope. Binnen func() heeft u toegang tot de lexical scope variabele myGlobal.

4. De closure

Ok, de lexical scope maakt het mogelijk om statisch toegang te krijgen tot de variabelen van de outer scopes. Er is nog maar één stap tot de closure!

Laten we nog eens kijken naar het outerFunc() en innerFunc() voorbeeld:

function outerFunc() { let outerVar = 'I am outside!'; function innerFunc() { console.log(outerVar); // => logs "I am outside!" } innerFunc();}outerFunc();

Binnen de innerFunc() scope wordt de variabele outerVar benaderd vanuit de lexical scope. Dat is al bekend.

Merk op dat innerFunc() aanroeping gebeurt binnen zijn lexische scope (de scope van outerFunc()).

Laten we een wijziging aanbrengen: innerFunc() aan te roepen buiten zijn lexicale bereik (buiten outerFunc()). Zou innerFunc() nog steeds toegang kunnen krijgen tot outerVar?

Laten we de aanpassingen in de code snippet doorvoeren:

function outerFunc() { let outerVar = 'I am outside!'; function innerFunc() { console.log(outerVar); // => logs "I am outside!" } return innerFunc;}const myInnerFunc = outerFunc();myInnerFunc();

Nu wordt innerFunc() uitgevoerd buiten zijn lexicale bereik. En wat belangrijk is:

innerFunc() heeft nog steeds toegang tot outerVar van zijn lexicale bereik, zelfs als het buiten zijn lexicale bereik wordt uitgevoerd.

Met andere woorden, innerFunc() sluit over (a.k.a. vangt, onthoudt) de variabele outerVar van zijn lexicale bereik.

Met andere woorden, innerFunc() is een closure omdat het de variabele outerVar uit zijn lexicale bereik oversluit.

De JavaScript closure

Je hebt de laatste stap gezet om te begrijpen wat een closure is:

De closure is een functie die toegang heeft tot zijn lexicale bereik, zelfs als deze buiten zijn lexicale bereik wordt uitgevoerd.

Simpeler gezegd, de closure is een functie die de variabelen onthoudt van de plaats waar hij is gedefinieerd, ongeacht waar hij later wordt uitgevoerd.

Een vuistregel om een closure te herkennen: als je in een functie een vreemde variabele ziet (die niet in de functie is gedefinieerd), is die functie hoogstwaarschijnlijk een closure, omdat de vreemde variabele wordt opgevangen.

In de vorige codefragment, outerVar is een alien variabele binnen de closure innerFunc() gevangen uit outerFunc() scope.

Laten we verder gaan met voorbeelden die laten zien waarom de closure nuttig is.

5. Voorbeelden van closure

5.1 Event handler

Laten we eens weergeven hoe vaak er op een knop is geklikt:

let countClicked = 0;myButton.addEventListener('click', function handleClick() { countClicked++; myText.innerText = `You clicked ${countClicked} times`;});

Open de demo en klik op de knop. De tekst wordt bijgewerkt om het aantal klikken weer te geven.

Wanneer op de knop wordt geklikt, wordt handleClick() ergens binnen de DOM-code uitgevoerd. De uitvoering gebeurt ver van de plaats van definitie.

Maar omdat het een closure is, vangt handleClick()countClicked op uit de lexical scope en werkt het bij wanneer er op wordt geklikt. Sterker nog, myText wordt ook opgevangen.

5.2 Callbacks

Het afvangen van variabelen uit de lexical scope is nuttig in callbacks.

Een setTimeout() callback:

const message = 'Hello, World!';setTimeout(function callback() { console.log(message); // logs "Hello, World!"}, 1000);

De callback() is een closure omdat het de variabele message afvangt.

Een iteratorfunctie voor forEach():

let countEven = 0;const items = ;items.forEach(function iterator(number) { if (number % 2 === 0) { countEven++; }});countEven; // => 2

De iterator is een closure omdat deze de variabele countEven vangt.

5.3 Functioneel programmeren

Currying gebeurt wanneer een functie een andere functie retourneert totdat de argumenten volledig zijn aangeleverd.

Voorbeeld:

function multiply(a) { return function executeMultiply(b) { return a * b; }}const double = multiply(2);double(3); // => 6double(5); // => 10const triple = multiply(3);triple(4); // => 12

multiply is een curried functie die een andere functie retourneert.

Currying, een belangrijk concept van functioneel programmeren, is ook mogelijk dankzij closures.

executeMultiply(b) is een closure die a uit zijn lexicale bereik haalt. Wanneer de closure wordt aangeroepen, worden de gevangen variabele a en de parameter b gebruikt om a * b te berekenen.

6. Conclusie

Het bereik is wat de toegankelijkheid van variabelen in JavaScript regelt. Er kan een functie of een blok scope zijn.

De lexicale scope staat een functie scope toe om statisch toegang te krijgen tot de variabelen van de buitenste scopes.

Eindelijk is een closure een functie die variabelen van zijn lexical scope opvangt. In eenvoudige woorden, de closure onthoudt de variabelen van de plaats waar hij is gedefinieerd, ongeacht waar hij wordt uitgevoerd.

Closures vangen variabelen op in event handlers, callbacks. Ze worden gebruikt in functioneel programmeren. Bovendien zou je tijdens een sollicitatiegesprek voor Frontend kunnen worden gevraagd hoe closures werken.

Iedere JavaScript ontwikkelaar moet weten hoe closures werken. Deal with it ⌐■■.

Wat dacht je van een uitdaging? 7 Interview Vragen over JavaScript Closures. Kun je ze beantwoorden?

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *