Callbacks for events on webpages
Closures are extremely useful when callbacks are needed for event handling.
Consider the example from MDN of changing text size on a webpage at the press of a button.
First we define the CSS
body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; }
In the example the interactive text size buttons can change the font-size
property of the body
element, and the adjustments will be picked up by other elements on the page thanks to the relative units.
The javascript to do this is shown here
function makeSizer(size) { return function() { // This changes the font size of the entire <body> not to be confused with <header> document.body.style.fontSize = size + 'px'; }; } var size12 = makeSizer(12); var size14 = makeSizer(14); var size16 = makeSizer(16); document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16;
The callbacks are setup on lines 13-15.
The HTML that this script manipulates is shown here
<p>Some paragraph text</p> <h1>some heading 1 text</h1> <h2>some heading 2 text</h2> <a href="#" id="size-12">12</a> <a href="#" id="size-14">14</a> <a href="#" id="size-16">16</a>
Do not confuse <h1>, <h2> etc with <header>. <h1> through to <h6> are termed heading elements and are part of the <body>. So changing the style of the body, changes the style of all child elements.
Public and Private functions - the Module pattern
Javascript doesn't naturally support the idea of public and private functions but using closures we can quite easily emulate the idea.
Consider the following snippet of code
var counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } }; })(); console.log(counter.value()); // logs 0 counter.increment(); counter.increment(); console.log(counter.value()); // logs 2 counter.decrement(); console.log(counter.value()); // logs 1
The return statement at line 6 returns a set of named closures. What is interesting an annoying is that line 3 breaks javascripts own rules on scope. If you create a variable without the var keyword it has global scope, with var it has local scope. For the closure changeBy it has local scope it would seem. So let's try an experiment -
If we change the above to the following
var counter = (function() { var privateCounter = 0; bb = function changeBy(val) { privateCounter += val; } bb(3); return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } }; })(); alert(counter.value()); // logs 0 counter.increment(); counter.increment(); alert(counter.value()); // logs 2 counter.decrement(); alert(counter.value()); // logs 1
The calls to changeBy (lines 9 and 12) all fail, the exception thrown is that changeBy is undefined. How strange is that? I'm not sure what the lexical rules are but what I've observed are
Lexical rules through observation
- within a lexical environment, a value assigned to a variable that s not prefixed with var is globally available, so it pollutes the global namespace
- within a lexical environment, a value assigned to a var variable is not globally available
- within a lexical environment, a function that is assigned to variable (with or without var) is not available outside of the lexical environment unless it is returned using the return keyword