Jag förklarar vad omslutningar (Engelska "closures") är för något ...

Kan du hitta buggen i koden nedan ?

// Skapa mönster av texturerna
for(var i=0; i < texturer.length; i++) {
  texturer[i].onload = function(i) {
    mönster[i] = ctx.createPattern(texturer[i], 'repeat');
  }
}

Koden loopar igenom en lista med texturer och när texturen laddats skapas ett mönster.

Problemet är att när texturerna har laddats kommer loopen redan vara klar för länge sedan, och variabeln i kommer vara lika med texturer.length!

Självkallande funktioner

Det går att lösa problemet genom att använda en funktion som kallar sig själv (Engelska: "self calling function").

// Skapa mönster av texturerna
for(var i=0; i < texturer.length; i++) {
  texturer[i].onload = (function(index) {
    return function() {
      mönster[index] = ctx.createPattern(texturer[index], 'repeat');
    };
  }
})(i);

En funktion skapas och tilldelas parametern i, och denna funktion kallar sedan sig själv.
Funktioner som kallar sig själv kan vara användbara för att förhindra variabler från att läcka ut i det globala definitionsområdet. (Engelska: "global scope"). Men i detta fall gör den bara koden mer komplicerad.
Det går att skriva om funktionen mer begripligt så här:

// Skapa mönster av texturerna
for(var i=0; i < texturer.length; i++) {
  skapaMönster(i);
}

function skapaMönster(i) {
  texturer[i].onload = function() {
    mönster[i] = ctx.createPattern(texturer[i], 'repeat');
  }
}

Nästa gång du kommer på dig att göra en själv-kallande funktion i en for-loop: Lyft i stället ut funktionen och ge den ett namn. Och kalla/anropa funktionen i for-loopen, så att variabeln hamnar i funktionens parametrar!

Självkallande funktioner kallas ibland för "closures" vilket är förvirrande, så kalla dem bara för självkallande-funktioner eller funktioner-som-anropar-sig-själv, eller Engelska "self calling function".

"Closures" i JavaScript

I JavaScript och många andra programmerings-språk finns det något som kallas för "closure". Översatt till Svenska blir det "omslutning".

Varje gång en funktion skapas, skapas även en closure! (omslutning)

Omslutningen gör att funktionen får tillgång till alla variabler som har deklarerats i definitionsområdet, samt även dess föräldrars variabler, farföräldrars variabler, och så vidare. Detta kallas på Engelska för "lexical scope".

var global = 1;
function farfar() {
  var kakor = 1;
  function pappa() {
    var bullar = 2;
    // kan komma åt kakor och bullar, samt globala variabler
    
    function barn() {
      // kan också komma åt kakor och bullar, samt globala variabler
    }
  }
}

Variabler som deklareras med var i JavaScript får funktionen-som-den-deklarerades-i som definitionsområde. (Engelska "funktion scope")

I och med EcmaScript 2015 (ES 6) kan man även skapa variabler med let och const, som får kod-blocket {innanför måsvingarna} som definitionsområde.

Fördelar med closure

Det fina med closures/omslutningar är att även om funktionen kallas från ett annat definitionsområde, eller i ett senare skede, har den fortfarande tillgång till variablerna och funktionerna från definitionsområdet där den skapades. Detta är mycket användbart, speciellt i asynk kod.

function skapaKnapp() {
  var knapp = document.createElement("button");
  knapp.appendChild(document.createTextNode("Klicka på mig!"));
  document.body.appendChild(knapp);
  
  knapp.onclick = function klicka() {
    knapp.appendChild(document.createTextNode(" mer!"));
  }
  
  return knapp;
}

Även fast funktionen klicka körs i ett senare skede, kommer den ha tillgång till variabeln knapp.

let i for-loop

Variabler deklarerade med let inuti for-loopar fungerar på ett speciellt sätt ... För varje closure i loopen skapas en ny variabel som omslutningen får tillgång till!

for(let i=0; i<3; i++) setTimeout( function log() { console.log(i) }, 1000);

Efter en sekund skrivs 0,1,2 ut konsolen, eftersom både en ny variabel, och en ny funktion skapas i varje loop.
Detta beteende specificerades i EcmaScript 2015 (ES 6).

Personligen föredrar jag fortfarande att använda var, och kalla en funktion, så att variabeln (i) hamnar i funktionens (logSenare) parameter:

for(var i=0; i<3; i++) logSenare(i);

function logSenare(i) {
  setTimeout(function log() {
    console.log(i)
  }, 1000);
}



Följ mig via RSS:   RSS https://zäta.com/rss.xml (ange adressen i din feed-läsare)