Veel moderne web toepassingen maken intensief gebruik van JavaScript en JavaScript frameworks. Voor ASP.NET ontwikkelaars is het daarom belangrijk om te weten wat Closures zijn en zogeheten Immediately Invoked Function Expressions (IIFE). In dit artikel leer je wat bedoeld wordt met deze twee termen.
JavaScript Closures
Een closure is eigenlijk niets meer of minder dan een functie in een functie. Deze (interne) functie heeft toegang tot de parameters en variabelen van de omvattende functie. Bovendien blijft de staat bewaard, zelfs als de omvattende functie klaar is. Het volgende voorbeeld laat zien hoe dat in de praktijk werkt.
function ExterneFunctie(msg)
{
function InterneFunctie()
{
alert(msg);
}
InterneFunctie();
}
ExterneFunctie("Hallo Wereld!");
Zoals je ziet accepteert de omvattende functie een parameter msg. Deze parameter msg wordt gebruikt door de interne functie, die de code bevat om iets met die parameter te doen. De externe functie voert de interne functie uit. Code buiten de externe functie heeft geen toegang tot de interne functie.
Dit kunnen we gebruiken in wat complexere scenario's.
function FormatMessage()
{
var msg = "";
var obj = {};
obj.setMsg = function(value)
{
msg = value;
}
obj.getMsg = function()
{
return "Hallo " + msg;
}
return obj;
}
var formatter = FormatMessage();
formatter.setMsg("Wereld!");
alert(formatter.getMsg());
Je ziet dat de functie FormatMessage() een variabele msg declareert in de eigen (private) scope. Daarna wordt een JavaScript object gemaakt, waaraan twee functie-eigenschappen zijn toegevoegd: setMsg() en getMsg(). De functie setMsg() geeft de variabele msg een nieuwe waarde. De functie getMsg() geeft de waarde (met wat extra's) terug aan de aanroep.
Aan het einde van de functie FormatMessage wordt het object obj wordt teruggegeven aan de aanroep. Deze functie is dan klaar. Omdat FormatMessage() is beëindigd, zou je verwachten dat je geen gebruik meer kunt maken van de variabele msg. Maar formatter.setMsg() en formatter.getMsg() werken nog steeds.
De vraag is nu: zijn closures nu handig? En waarvoor dan? Een praktisch effect van closures is dat je data kunt verbinden met een functie die iets met die data doet. Dat lijkt een beetje op object-geörienteerd programmeren. De objecten stellen je in staat om data (de eigenschappen van een object) te koppelen aan een of meer methoden.
Je kunt dus een closure gebruiken op die plekken waar je anders een object met een enkele methode zou gebruiken. En in web-toepassingen kom je die behoefte vaak tegen. Veel JavaScript code op het web maakt gebruik van events. We definiëren een bepaalde actie, en koppelen die actie aan een event (een muisklik, scroll) dat getriggerd wordt door de gebruiker. De actie wordt vaak gekoppeld in de vorm van een callback. D.w.z. een enkele functie die wordt uitgevoerd als antwoord op een event.
Stel dat we een functie in een webpagina willen hebben die het mogelijk maakt een stuk tekst groter of kleiner te maken. Hier is een stukje JavaScript dat dit kan doen (voor een werkend voorbeeld, zie: https://jsfiddle.net/d9bd3hmq/).
function TextSize() {
var size = 16;
var obj = {};
obj.bigger = function () {
size++;
document.body.style.fontSize = size + 'px';
};
obj.smaller = function () {
size--;
document.body.style.fontSize = size + 'px';
};
return obj;
}
var textSize = TextSize();
document.getElementById('groter').onclick = textSize.bigger;
document.getElementById('kleiner').onclick = textSize.smaller;
Het is door deze werking van closures dat dit principe door frameworks als jQuery en AngularJS veel worden gebruikt om de code te organizeren.
Immediately Invoked Function Expressions
Een Immediately Invoked Function Expression is een JavaScript functie die direct wordt uitgevoerd zodra het is gemaakt. We beginnen met een stukje klassiek JavaScript.
function DoWork()
{
var msg = "Hallo Wereld!";
alert(msg);
}
var displayMessage = function()
{
var msg = "Hallo Wereld!";
alert(msg);
}
DoWork();
displayMessage();
De bovenstaande code bevat een functie met de naam DoWork(). Je ziet ook een anonieme functie, die in een variabele wordt gestopt: displayMessage. Vervolgens worden deze twee functies aangeroepen. Dat betekent dat het definiëren van de functie en het uitvoeren ervan twee aparte stappen zijn.
Door de code te schrijven als een IIFE wordt de functie gedefiniëerd en gelijk uitgevoerd. Dat ziet er zo uit.
(function ()
{
var msg = "Hallo Wereld";
alert(msg);
})();
In deze code staat een anonieme functie. De hele functie staat tussen haakjes, en wordt gelijk gevolgd door (). Op deze manier wordt de definitie en de uitvoering gecombineerd. Je kunt de functie natuurlijk ook uitbreiden, net als elke andere JavaScript functie.
(function (msg)
{
alert(msg);
})("Hallo Wereld");
Nu staat de variabele msg niet meer in de functie, maar wordt deze als parameter doorgegeven aan de functie.
Als je een jQuery plugin wil maken, of als je de implementatie van een bestaande plugin eens goed bekeken hebt, dan zie je dat deze vaak het IIFE patroon hanteren. Zo'n jQuery plugin begin namelijk meestal zo:
(function ($) {
$.fn.devTipsPlugin = function (settings) {
//plugin code hier
}
})(jQuery);
Zoals je ziet in dit voorbeeld van een jQuery plugin bevat de code een anonieme functie met een enkele parameter: $. Merk op dat $ een valide naam is voor een variabele in JavaScript. Het echt werk van de plugin gaat zitten in de implementatie van de anonieme functie. Deze anonieme functie wordt toegewegen aan de variabele devTipsPlugin.
De functie wordt na de definitie gelijk uitgevoerd, en krijgt het jQuery object mee als parameter. Binnen de plugin wordt aan het jQuery object gerefereerd via de variabele $. Op deze manier zorgen we ervoor dat de betekenis van $ behouden blijft voor de hele implementatie van de plugin.
Meer informatie
Wil je meer weten over closures, check dan deze links:
Meer informatie over IIFE vind je hier: