21. februar 2026

Variable scope, closure

JavaScript er et meget funktionsorienteret sprog. Det giver os en masse frihed. En funktion kan skabes når som helst, overføres som argument til en anden funktion, og derefter kaldes fra et helt andet sted i koden.

Vi ved allerede, at en funktion kan tilgå variabler uden for den (“outer” variables).

Men hvad sker der hvis de ydre variabler ændres efter en funktion er skabt? Vil funktionen få de nyere værdier eller de gamle?

Og hvad hvis en funktion gives som et argument til en anden funktion og derefter kaldes fra et andet sted i koden, vil den da have adgang til de ydre variabler på det nye sted?

Lad os udvide forståelsen af funktioner for at håndtere mere komplekse situationer der gør brug af dem.

Vi taler om let/const variable her

I JavaScript er der tre måder at deklarere en variabel: let, const (de moderne måder), og var (et l"evn fra fortiden").

  • I denne artikel bruger vi let til at deklarere variable i eksemplerne.
  • Variable der er deklareret med const, opfører sig på samme måde, så denne artikel gælder også for dem.
  • Den traditionelle var har en lidt anderledes opførsel, som beskrives i artiklen Den gamle "var".

Kodeblokke

Hvis en variabel er deklareret inden for et kodeblok {...}, er den kun synlig inde i den blok.

For eksempel:

{
  // udfør kode med lokale variable der ikke skal være synlige uden for blokken

  let message = "Hej"; // kun synlig inde i denne blok

  alert(message); // Hej
}

alert(message); // Fejl: message er ikke defineret

Vi kan bruge dette til at isolere en del af koden, som har sin egen opgave og variable der kun tilhører den:

{
  // vis besked
  let message = "Hej";
  alert(message);
}

{
  // vis en anden besked
  let message = "Farvel";
  alert(message);
}
Der ville være fejl uden blokke

Bemærk at uden separate blokke ville der være en fejl, hvis vi bruger let med en eksisterende variabelnavn:

// vis besked
let message = "Hej";
alert(message);

// vis en anden besked
let message = "Farvel"; // Fejl: variabel allerede deklareret
alert(message);

For if, for, while og så videre gælder det også at variable deklareret inden for {...} er kun synlige inde i blokken:

if (true) {
  let phrase = "Hej!";

  alert(phrase); // Hej!
}

alert(phrase); // Fejl, variablen findes ikke!

Fordi if-blokken er færdig kan alert nedenfor ikke se phrase – derfor fejlen.

Det er perfekt da det tillader os at deklarere lokale variable, specifikke for hver if-gren.

Det samme gælder for for og while løkker:

for (let i = 0; i < 3; i++) {
  // variablen i er kun synlig inde i denne for-løkke
  alert(i); // 0, så 1, så 2
}

alert(i); // Fejl, ingen sådan variabel

Visuelt er let i uden for de krøllede parenteser {...} men for konstruktionen er speciel her: Variablen der deklareres i den ses som en del af kodeblokken.

Indlejrede funktioner

En funktion kaldes “indlejret” (nested) når den er oprettet inde i en anden funktion.

Det er nemt at gøre dette med JavaScript.

Vi kan bruge det til at organisere vores kode, som dette:

function sayHiBye(firstName, lastName) {

  // Indlejret hjælpefunktion der bruges til at generere fulde navn
  function getFullName() {
    return firstName + " " + lastName;
  }

  alert( "Hello, " + getFullName() );
  alert( "Bye, " + getFullName() );

}

Her er den indlejrede funktion getFullName() oprettet for overskuelighed. Den kan tilgå de ydre variable og returnere det fulde navn. Indlejrede funktioner er meget almindelige i JavaScript.

Hvad er endnu mere interessant, en indlejret funktion kan returneres: enten som en egenskab af et nyt objekt eller som resultat af sig selv. Den kan derefter bruges et andet sted. Uanset hvor, den har stadig adgang til de samme ydre variable.

Nedenfor opretter makeCounter funktionen “counter” der returnerer næste tal ved hvert kald:

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2

Udover at være simpel, har små ændrede varianter af denne kode praktiske anvendelser, for eksempel som en random number generator til at generere tilfældige værdier til automatiserede tests.

Hvordan virker dette? Hvis vi opretter flere counters, vil de være uafhængige? Hvad sker der med variablene her?

At forstå denne slags er godt for den generelle forståelse af JavaScripts måde at arbejde med variable og funktioner. Det er også god viden for at kunne arbejde med mere komplekse scenarier. Så lad os gå lidt mere i dybden.

Leksikalt miljø (Lexical Environment)

Here be dragons!

Den dybere tekniske forklaring ligger foran os.

Selv om jeg gerne vil undgå detaljer om lavniveau sprog, vil enhver forståelse uden dem være mangelfuld og ufuldstændig, så gør jer klar.

For overskuelighedens skyld, er forklaringen opdelt i flere trin.

Trin 1. Variable

I JavaScript har enhver kørende funktion, kodeblok {...}, og hele scriptet et internt (skjult) tilknyttet objekt kendt som Leksikalt miljø (Lexical Environment).

Det leksikale miljø består af to dele:

  1. Environment Record – et objekt som gemmer alle lokale variable som dens egenskaber (og nogle andre informationer som værdien af this).
  2. En reference til det ydre leksikale miljø, det som er forbundet med ydre kode.

En “variabel” er bare en egenskab af det specielle interne objekt, Environment Record. “At hente eller ændre en variabel” betyder “at hente eller ændre en egenskab af det objekt”.

I denne simple kode uden funktioner, findes der kun ét leksikalt miljø:

Dette er det såkaldte globale leksikale miljø, forbundet med hele scriptet.

På billedet ovenfor, betyder rektanglen Environment Record (variable store) og pilen ydre reference. Det globale leksikale miljø har ingen ydre reference, derfor peger pilen til null.

Mens koden afvikles, ændres det leksikale miljø.

Her er en lidt længere kode:

Rektanglerne på højre side demonstrerer hvordan det globale leksikale miljø ændrer sig under afvikling:

  1. Når scriptet starter, er det leksikale miljø forudfyldt med alle deklarerede variable.
    • Oprindeligt er de i “Uninitialized” tilstanden. Det er en speciel intern tilstand, det betyder at motoren kender til variablen, men den ikke kan refereres før den er deklareret med let. Det er næsten det samme som om variablen ikke eksisterede.
  2. Derefter optræder let phrase definitionen. Der er ingen tildeling endnu, så dens værdi er undefined. Vi kan bruge variablen fra dette tidspunkt og frem.
  3. phrase tildeler en værdi.
  4. phrase ændrer værdien.

Alting virker simpelt for nu, ikke?

  • En variabel er en egenskab af et specielt internt objekt, forbundet med det aktuelt kørende blok/funktion/script.
  • Arbejde med variable er faktisk arbejde med egenskaberne af det objekt.
Det leksikale miljø er et specifikationsobjekt

Det “leksikale miljø” er et specifikationsobjekt: det eksisterer kun “teoretisk” i sprogspecifikationen for at beskrive hvordan ting fungerer. Vi kan ikke få adgang til dette objekt i vores kode og manipulere det direkte.

JavaScript-motorer kan også optimere det, fjerne variable som ikke bruges for at spare hukommelse og udføre andre interne tricks, så længe det synlige adfærd forbliver som beskrevet.

Trin 2. Deklarering af funktioner

En funktion er også en værdi, ligesom en variabel.

Forskellen er at en Function Declaration er øjeblikkeligt fuldt initialiseret.

Når et leksikalt miljø oprettes, bliver en Function Declaration øjeblikkeligt til en klar til brug funktion (forskellig fra let, som ikke kan bruges før deklarationen).

Når et leksikalt miljø oprettes, bliver en Function Declaration øjeblikkeligt til en klar til brug funktion (forskellig fra let, som ikke kan bruges før deklarationen).

Det er derfor vi kan bruge en funktion, deklareret som Function Declaration, selv før deklarationen selv.

For eksempel, her er det oprindelige tilstand af det globale leksikale miljø, når vi tilføjer en funktion:

Denne adfærd gælder kun for Function Declarations, ikke for Function Expressions hvor vi tildeler en funktion til en variabel, såsom let say = function(name)....

Trin 3. Indre og ydre leksikale miljøer

Når en funktion kører, oprettes et nyt leksikalt miljø automatisk for at gemme lokale variable og parametre for kaldet.

For eksempel, for say("John"), ser det ud som følger (køretidspunktet er markeret med en pil):

Ved kald af funktionen har vi to leksikale miljøer: det indre (for funktionens kald) og det ydre (globalt):

  • Det indre leksikale miljø svarer til den nuværende udførelse af say. Det har en enkelt egenskab: name, funktionens argument. Vi kalder say("John"), så værdien af name er "John".
  • Det ydre leksikale miljø er det globale leksikale miljø. Det har variablen phrase og selve funktionen.

Det indre leksikale miljø har en reference til det ydre.

Når koden ønsker at tilgå en variabel – søges det indre leksikale miljø først, derefter det ydre, og så videre indtil det globale miljø.

Hvis en variabel ikke findes nogen steder, er det en fejl i strict mode (uden use strict, vil en tildeling til en ikke-eksisterende variabel skabe en ny global variabel, for kompatibilitet med gammel kode).

I dette eksempel går søgningen som følger:

  • For variablen name, finder alert inden i say den øjeblikkeligt i det indre leksikale miljø.
  • Når den forsøger at tilgå phrase, så findes den ikke lokalt, så den følger referencen til det ydre leksikale miljø og finder den der.

Trin 4. Returnering af en funktion

Lad os vende tilbage til eksemplet med makeCounter.

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

I begyndelsen af hvert makeCounter() kald oprettes et nyt leksikalt miljøobjekt, for at gemme variabler for dette makeCounter kald.

Så vi har to indlejrede leksikale miljøer, lige som i eksemplet ovenfor:

Hvad der er forskelligt her er at der ved eksekvering af makeCounter() oprettes en lille indlejret funktion der kun indeholder en linje: return count++. Vi kører den ikke, den oprettes kun.

Alle funktioner husker det leksikale miljø hvor de blev oprettet. Teknisk set er der ingen magi her: alle funktioner har den skjulte egenskab kaldet [[Environment]], som holder referencen til det leksikale miljø hvor funktionen blev oprettet:

counter.[[Environment]] har referencen til {count: 0} leksikale miljø. Det er sådan funktionen husker hvor den blev oprettet, uanset hvor den kaldes. Referencen [[Environment]] sættes én gang og for altid ved oprettelsen af funktionen.

Senere, når counter() kaldes, oprettes et nyt leksikalt miljø for kaldet, og dens ydre leksikale miljøreference tages fra counter.[[Environment]]:

Når koden indeni counter() søger efter count variablen, søger den først i sit eget leksikale miljø (tomt, da der ikke er lokale variabler der), derefter i det ydre leksikale miljø af det ydre makeCounter() kald, hvor den finder og ændrer den.

En variabel er opdateret i det leksikale miljø hvor den lever.

Her er tilstanden efter eksekveringen af counter():

Hvis vi kalder counter() flere gange, vil count variablen blive øget til 2, 3 og så videre, på samme sted.

Closure

Der er et generelt term i programmering kaldet “closure”, som udviklere generelt bør kende.

En closure er en funktion der husker sine ydre variable og kan tilgå dem. I nogle sprog er det ikke muligt, eller en funktion skal skrives på en speciel måde for at det skal virke. Men som forklaret ovenfor, i JavaScript, er alle funktioner naturligt closures (der er kun ét undtagelse, som beskrives i Syntaksen "new Function").

Det vil sige: de husker automatisk hvor de blev oprettet ved hjælp af en skjult [[Environment]] egenskab, og derefter kan deres kode tilgå ydre variabler.

Når en frontend udvikler stilles et spørgsmål om “Hvad er en closure?”, så er et gyldigt svar en definition af closure og en forklaring på at alle funktioner i JavaScript er closures, og måske et par flere ord om tekniske detaljer: egenskaben [[Environment]] og hvordan Lexical Environments virker.

Garbage collection

Normalt vil et leksikalt miljø blive fjernet fra hukommelsen sammen med alle variablerne efter at funktionen er afsluttet. Det skyldes at der ikke er nogen referencer til det. Som ethvert JavaScript objekt, bliver det kun beholdt i hukommelsen så længe det er tilgængeligt.

Men, hvis der er en indlejret funktion som stadig er tilgængelig efter afslutningen af en funktion, så har den en [[Environment]] egenskab som refererer til det leksikale miljø.

I dette tilfælde er det leksikale miljø stadig tilgængeligt selv efter afslutningen af funktionen, så det bliver beholdt i hukommelsen.

For example:

function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f(); // g.[[Environment]] gemmer en reference til det leksikale miljø
// af det tilsvarende f() kald

Bemærk at hvis f() kaldes mange gange, og de resulterende funktioner gemmes, så vil alle tilsvarende leksikale miljøobjekter også blive beholdt i hukommelsen. I koden nedenfor, vil alle 3 af dem gemmes:

function f() {
  let value = Math.random();

  return function() { alert(value); };
}

// 3 funktioner i et array. Hvert af dem linjer til et leksikalt miljø
// af det tilsvarende f() kald
let arr = [f(), f(), f()];

Et leksikalt miljøobjekt dør når det bliver utilgængeligt (ligesom ethvert andet objekt). Med andre ord eksisterer det kun mens der er mindst én indlejret funktion der refererer til det.

I koden nedenfor, efter at den indlejrede funktion er fjernet, bliver dets omkringliggende leksikale miljø (og dermed value) ryddet fra hukommelsen:

function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f(); // mens funktionen g eksisterer, bliver value beholdt i hukommelsen

g = null; // ...og nu bliver hukommelsen ryddet

Optimeringer (lidt for meget i virkeligheden)

Som vi har set, i teorien eksisterer alle ydre variabler så længe en funktion lever.

Men i praksis forsøger JavaScript-motorer at optimere det. De analyserer brugen af variabler og hvis det er klart fra koden at en ydre variabel ikke bruges – bliver den fjernet.

En vigtig sideeffekt i V8 (Chrome, Edge, Opera) er at en sådan variabel ikke længere er tilgængelig i debugging.

Prøv at køre følgende eksempel i Chrome med Developer Tools åbnet.

Når det stopper, skriv alert(value) i konsollen.

function f() {
  let value = Math.random();

  function g() {
    debugger; // i konsollen: skriv alert(value); No such variable!
  }

  return g;
}

let g = f();
g();

Som du kan se – der er ingen sådan variabel! I teorien burde den være tilgængelig, men motoren har optimiseret den ud.

Det kan føre til morsomme (eller ikke så sjove) fejl i debugging. En af dem er at vi vil kunne se en ydre variabel der er navngivet med samme navn i stedet for den forventede:

let value = "Overrasket!";

function f() {
  let value = "den tætteste værdi";

  function g() {
    debugger; // i konsollen: skriv alert(value); Overrasket!
  }

  return g;
}

let g = f();
g();

Denne feature i V8 er god at kende til. Hvis du debugger med Chrome/Edge/Opera, vil du til sidst møde den.

Det er ikke en fejl i debuggere, men snarere en speciel feature i V8. Måske vil det blive ændret på et tidspunkt. Du kan altid tjekke for det ved at køre eksemplerne på denne side.

Opgaver

vigtighed: 5

Funktionen sayHi bruger en ekstern variabel name. Når funktionen kører, hvilken værdi vil den bruge?

let name = "John";

function sayHi() {
  alert("Hej, " + name);
}

name = "Pete";

sayHi(); // Hvad vil den vise: "John" eller "Pete"?

Sådanne situationer er udbredte både i browser- og server-side udvikling. En funktion kan blive planlagt til at køre senere end den blev oprettet, for eksempel efter en brugerhandling eller en netværksforespørgsel.

Så spørgsmålet er: vil den tage de seneste ændringer?

The answer is: Pete.

En funktion får ydre variabler som de er nu, den bruger de mest nyeste værdier.

Gamle variabelværdier gemmes ikke nogen steder. Når en funktion ønsker en variabel, tager den den nuværende værdi fra sit eget leksikale miljø eller det ydre.

vigtighed: 5

Funktionen makeWorker nedenfor opretter en anden funktion og returnerer den. Den nye funktion kan kaldes fra et andet sted.

Vil den have adgang til de ydre variable fra sit oprettelsessted, eller fra sit kaldested, eller begge?

function makeWorker() {
  let name = "Pete";

  return function() {
    alert(name);
  };
}

let name = "John";

// Opret en funktion
let work = makeWorker();

// Kald den
work(); // Hvad vil den vise?

Hvilken værdi vil den vise: “Pete” eller “John”?

Svaret er: Pete.

Funktionen work() i koden nedenfor får name fra stedet hvor den blev oprettet gennem referencen til det ydre leksikale miljø:

Så resultatet er "Pete" her.

Men, hvis der ikke var let name i makeWorker(), så ville søgningen gå udenfor og tage den globale variabel som vi kan se fra kæden ovenfor. I det tilfælde ville resultatet være "John".

vigtighed: 5

Her opretter vi to tællere: counter og counter2 ved brug af samme makeCounter funktion.

Er de uafhængige? Hvad vil den anden tæller vise? 0,1 eller 2,3 eller noget andet?

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();
let counter2 = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1

alert( counter2() ); // ?
alert( counter2() ); // ?

Svaret er: 0,1.

Funktionen counter og counter2 er skabt af forskellige kald af makeCounter.

Så de har uafhængige ydre leksikale miljøer, hver har sin egen count.

vigtighed: 5

Her er et counter objekt oprettet ved hans hjælp af constructor funktionen.

Hvordan vil det fungere? Hvad vil det vise?

function Counter() {
  let count = 0;

  this.up = function() {
    return ++count;
  };
  this.down = function() {
    return --count;
  };
}

let counter = new Counter();

alert( counter.up() ); // ?
alert( counter.up() ); // ?
alert( counter.down() ); // ?

Det vil fungere helt fint.

Begge indlejrede funktioner er oprettet i det samme ydre leksikale miljø, så de deler adgang til den samme count variabel:

function Counter() {
  let count = 0;

  this.up = function() {
    return ++count;
  };

  this.down = function() {
    return --count;
  };
}

let counter = new Counter();

alert( counter.up() ); // 1
alert( counter.up() ); // 2
alert( counter.down() ); // 1
vigtighed: 5

Se på følgende kode. Hvad vil resultatet være af kaldet på den sidste linje?

let phrase = "Hello";

if (true) {
  let user = "John";

  function sayHi() {
    alert(`${phrase}, ${user}`);
  }
}

sayHi();

Resultatet er en fejl.

Funktionen sayHi er deklareret inden for if, så den lever kun inden for det. Der er ingen sayHi uden for if.

vigtighed: 4

Skriv funktionen sum som virker sådan her: sum(a)(b) = a+b.

Ja, præcis på denne måde, ved brug af dobbelte parenteser (ikke en fejl).

For eksempel:

sum(1)(2) = 3
sum(5)(-1) = 4

For at den anden sætning med parenteser skal fungere, skal den første returnere en funktion.

Sådan her:

function sum(a) {

  return function(b) {
    return a + b; // tager "a" fra det ydre leksikale miljø
  };

}

alert( sum(1)(2) ); // 3
alert( sum(5)(-1) ); // 4
vigtighed: 4

Hvad vil resultatet være af denne kode?

let x = 1;

function func() {
  console.log(x); // ?

  let x = 2;
}

func();

P.S. Der er en lille fælde i denne opgave. Løsningen er ikke åbenlys.

Resultater er: fejl.

Prøv at køre den:

let x = 1;

function func() {
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
  let x = 2;
}

func();

I eksemplet kan vi observere den lille forskel mellem en “ikke eksisterende” og en “ikke-initialiseret” variabel.

Som du måske har læst i artiklen Variable scope, closure, starter en variabel i “ikke-initialiseret” tilstand fra det øjeblik, hvor udførelsen indtager et kodeblok (eller en funktion). Og den forbliver ikke-initialiseret indtil den tilsvarende let-erklæring.

Med andre ord, en variabel eksisterer teknisk set, men kan ikke bruges før let.

Koden ovenfor demonstrerer dette.

function func() {
  // den lokale variable x er kendt af motoren fra det øjeblik funktionen starter,
  // men "ikke-initialiseret" (ubrugelig) indtil let ("dead zone")
  // derfor fejlen

  console.log(x); // ReferenceError: Cannot access 'x' before initialization

  let x = 2;
}

Denne zone af midlertidig ubrugelighed af en variabel (fra begyndelsen af kodeblokken til let) kaldes ofte “dødszonen” (dead zone).

vigtighed: 5

Vi har en indbygget metode arr.filter(f) til arrays. Den filtrerer alle elementer gennem funktionen f. Hvis den returnerer true, så returneres det element i det resulterende array.

Lav et sæt af “klar til brug” filtre som kan bruges med filter:

  • inBetween(a, b) – tallet er mellem a og b begge tal inklusiv.
  • inArray([...]) – i det givne array.

Brugen skal være i stil med dette:

  • arr.filter(inBetween(3,6)) – vælg kun værdier mellem 3 og 6.
  • arr.filter(inArray([1,2,3])) – vælg kun elementer som matcher med et af elementerne i [1,2,3].

For eksempel:

/* .. din kode for inBetween og inArray */
let arr = [1, 2, 3, 4, 5, 6, 7];

alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6

alert( arr.filter(inArray([1, 2, 10])) ); // 1,2

Åbn en sandbox med tests.

Filter inBetween

function inBetween(a, b) {
  return function(x) {
    return x >= a && x <= b;
  };
}

let arr = [1, 2, 3, 4, 5, 6, 7];
alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6

Filter inArray

function inArray(arr) {
  return function(x) {
    return arr.includes(x);
  };
}

let arr = [1, 2, 3, 4, 5, 6, 7];
alert( arr.filter(inArray([1, 2, 10])) ); // 1,2

Åbn løsningen med tests i en sandbox.

vigtighed: 5

Vi har et array af objekter, som skal sorteres:

let users = [
  { name: "John", age: 20, surname: "Johnson" },
  { name: "Pete", age: 18, surname: "Peterson" },
  { name: "Ann", age: 19, surname: "Hathaway" }
];

Den normale måde at gøre det på er:

// ved navn (Ann, John, Pete)
users.sort((a, b) => a.name > b.name ? 1 : -1);

// ved alder (Pete, Ann, John)
users.sort((a, b) => a.age > b.age ? 1 : -1);

Kan vi gøre det endnu mindre “udtalt” (verbose). Noget i stil med dette?

users.sort(byField('name'));
users.sort(byField('age'));

Så i stedet for at skrive en funktion, bare brug byField(fieldName).

Skriv funktionen byField som kan bruges til det.

Åbn en sandbox med tests.

function byField(fieldName){
  return (a, b) => a[fieldName] > b[fieldName] ? 1 : -1;
}

Åbn løsningen med tests i en sandbox.

vigtighed: 5

Den følgende kode opretter en array af shooters.

Hver funktion er ment at vise sit nummer. Men noget er forkert…

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function() { // opret en shooter funktion,
      alert( i ); // der skal vise sit nummer
    };
    shooters.push(shooter); // og tilføj den til arrayet
    i++;
  }

  // ...og returner arrayet af shooters
  return shooters;
}

let army = makeArmy();

// alle shooters viser 10 i stedet for deres tal 0, 1, 2, 3...
army[0](); // 10 fra shooter nummer 0
army[1](); // 10 fra shooter nummer 1
army[2](); // 10 ...og så videre.

Hvorfor får alle shooters samme værdi?

Fiks koden så koden virker som forventet.

Åbn en sandbox med tests.

Lad os undersøge hvad der faktisk sker inde i makeArmy. Måske står løsningen så klarere.

  1. Den opretter et tomt array shooters:

    let shooters = [];
  2. Fylder den med funktioner via shooters.push(function) i løkken.

    Hvert element er en funktion, så det resulterer i et array der ser således ud:

    shooters = [
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); }
    ];
  3. Arrayet returneres fra funktionen.

    Senere vil et kald til et element i arrayet, f.eks. army[5]() vil hente elementet army[5] fra arrayet (som er en funktion) og kalde den.

    Men, hvorfor viser alle sådanne funktioner så samme værdi, 10?

    Det skyldes, at der ikke er en lokal variabel i inde i shooter-funktionerne. Når en sådan funktion kaldes, tager den i fra dens ydre leksikale miljø. Så hvad er værdien af i når funktionen kaldes?

    Kig på koden:

    function makeArmy() {
      ...
      let i = 0;
      while (i < 10) {
        let shooter = function() { // shooter funktion
          alert( i ); // skal vise sit nummer
        };
        shooters.push(shooter); // tilføj funktionen til arrayet
        i++;
      }
      ...
    }

    Vi kan se at alle shooter funktioner er oprettet i det leksikale miljø af makeArmy() funktionen. Men når army[5]() kaldes, har makeArmy allerede afsluttet sin job, og den endelige værdi af i er 10 (while stopper ved i=10).

    Som resultat får alle shooter funktioner samme værdi fra det ydre leksikale miljø og det er den sidste værdi, i=10.

    Som du kan se ovenfor så oprettes der et nyt leksikalt miljø ved hver iteration af while {...} blokken. Så for at fikse dette, kan vi kopiere værdien af i til en variabel inden i while {...} blokken, som dette:

    function makeArmy() {
      let shooters = [];
    
      let i = 0;
      while (i < 10) {
          let j = i;
          let shooter = function() { // shooter funktion
            alert( j ); // skal vise sit nummer
          };
        shooters.push(shooter);
        i++;
      }
    
      return shooters;
    }
    
    let army = makeArmy();
    
    // Nu virker koden som den skal
    army[0](); // 0
    army[5](); // 5

    Her vil let j = i deklarere en lokal variabel j og kopiere i over i den. Primitiver kopieres “ved deres værdi”, så vi får en reelt uafhængig kopi af i, der tilhører den aktuelle løkkes iteration.

    Shooters virker korrekt nu fordi værdien af i nu lever et lidt tættere på. Ikke i makeArmy() Lexical Environment, men i det Lexical Environment der svarer til den aktuelle løkkes iteration:

    Dette problem kunne undgås hvis vi brugte for i stedet for while, som dette:

    function makeArmy() {
    
      let shooters = [];
    
      for(let i = 0; i < 10; i++) {
        let shooter = function() { // shooter funktion
          alert( i ); // bør vise sit nummer
        };
        shooters.push(shooter);
      }
    
      return shooters;
    }
    
    let army = makeArmy();
    
    army[0](); // 0
    army[5](); // 5

    Det er grundlæggende det samme fordi for gennem hver iteration genererer en ny leksikale miljø med sin egen variabel i. Så shooter genereret i hver iteration refererer til dens egen i, fra den pågældende iteration.

Nu, efter du har lagt så meget energi i at læse dette, og den endelige opskrift er så enkel – bare brug for, kan du måske tænke – var det værd det?

Vel, hvis du kunne svare spørgsmålet nemt, ville du ikke have læst løsningen. Så håber jeg, at denne opgave har hjulpet dig med at forstå tingene lidt bedre.

Desuden er der faktisk tilfælde hvor man hellere foretrækker while frem for for, og andre scenarier hvor sådanne problemer opstår.

Åbn løsningen med tests i en sandbox.

Tutorial-oversigt

Kommentarer

læs dette før du kommenterer…
  • Hvis du har forslag til forbedringer - så opret venligst et GitHub-issue eller en pull request i stedet for at kommentere.
  • Hvis du ikke forstår noget i artiklen - så uddyb venligst.
  • For at indsætte få ord kode, brug <code>-taggen, for flere linjer - omslut dem i <pre>-tag, for mere end 10 linjer - brug en sandbox (plnkr, jsbin, codepen…)