Today I'd like to investigate javascript closures. My first encounter with functional programming made my head spin, but since then, I have seen the power, elegance and utility of this programming paradigm.
First, there are many good discussions of javascript closures on the Web. This discussion does not pretend to trump any of them, but I had to learn it from my own angle. Here I will show some nuances I have encountered. There are many other related subjects here that I will not get into, like lexical scope, strict mode, silent failures or object oriented javascript.
There are some failed examples shown here, to show some pitfalls one should avoid. Obvserve closeley.
If you don't want to read through this to get the best version (on this page) of "add_one" click here
To over simplify (and lose some accuracy), a closure is a function enclosed within another function. The heart of functional programming is being able to pass around functions as values to be executed at will. This is great for event driven applications. Let's dive right in with the first example.
// add one, version one
function add_v1 (init) {
var val = init;
return function () {
val = val + 1;
console.log("val is " + val);
}
}
This is the most basic example I can come up with to implement a closure that adds 1. It
doesn't do much and is not very useful, but it will add one to the initial value
if you use it correctly. The outer function is named, but the
inner function is anonymous, or not named. The inner
function is the return value for add_v1
. Let's "kick the tires".
var a = add_v1
Notice there are no parenthasis ()
in this
line of code. Let's compare a to b, below. What is a?
a = function add_v1 (init) { var val = init; return function () { val = val + 1; console.log("val is " + val);}}
var b = add_v1(3);// b is a closure
What is b?We can run b
by itself, we don't need the variable a
or the
adder function. Here we have executed the adder function and assigned the result to
b
. Now we can run b
at will separately from
another variable, say c
shown below. Then each of b
and c
can retain
different values even though they run the same function.
b()
Our "add one version one" closure is used for the first time, but doesn't do much, not even write to the console. This demonstrates our first pitfall: our adder does not initialize the internal variable "val", or rather, initializes it to undefined, which will, in turn, give us a NaN when we try to manipulate it numerically, which is garbage for what we want to do.
var c = add_v1(4);
c();// run it, the internal variable "val" is now 5
The value of "val" in c
and b
now have different values. They were each initialized with different values and run once.
b = add_v1(3);// initialize
Here we started over and gave an initial value. The add_v1 function runs, but the internal
anoymous function does not. Therefore, nothing is logged to the console once again. The value of the internal variable val
is 3 after executing this line of code, but you can't see it unless you use your debugger.
a(3)();// original function with an initial value and execute the internal function
console -> val is 4
Finally, we have executed both functions - a and its closure - and text is written to the
console -> val is 4. b
and a
are
quite different.
a()();// bad, don't do this
console -> val is NaN
Pitfall. This code initializes val to undefined
and then tries
to add 1. val
is then NaN
.
a()(7);// bad, don't do this
console -> val is NaN
Pitfall. Almost the same as above, except we provide an argument to the closure, which it promptly ignores. The closure takes no arguments.
a("fred")();// bad, don't do this (unless that's what you really wanted)
console -> val is fred1
Pitfall. This is what you get when you try to add a string and a number. The function will take the argument, but javascript is weakly typed. This has strengths and weaknesses.
// add one, version two
function add_v2 (init) {
var val = init;
return function () {
val += 1;
console.log("val is " + val);
return val;
}
}
Here is a modified example from the previous in that the closure returns a value. It's a small difference in code with substantive implications.
var c = add_v2(21);
c();// val is 22
c();// val is 23
console -> val is 22
console -> val is 23
Now we can use our adder. What just happened? It's a bit
different than the previous example. Now that we're returning a value,
the return value can be used. When we execut c()
, we are actually executing the closure function within
its environment. What is c equal to? c =
function () {val += 1;console.log("val is " + val);return val;}
but we can't execute that function unless we know what the value of val
is. This is the major difference between a function and a closure. The closure
can be executed outside its original scope. The javascript engine
will make a reference to the environment in which it was initiated and the value of val
will be known.
// add one, version three
function add_v3 (init) {
var val = init;
return function () {
val += 1;
return val;
}
}
This one is not functionally different, I just left out the console logging
// add one, version four
var add_v4 = (function (init) {
var counter = init;
return function () {
counter += 1;
console.log("counter is " + counter);
return counter;
}
})(93);// invoke / initialize (this is possible one time only)
console -> counter is 94
Here, instead of naming the function, I assigned the closure to the variable
add_v4
. It works much the same way as the previous example, except
it can only be initiated once (unless you write the entire code block again) since
the name of the outer function is nowhere to be found. It is
an anonymous function. Since it was initialized to 93 and immediately
executed, the first console log shows the counter to be 94. Now what happens if
we execute this line?
add_v4(98);
The 98 is promptly ignored. 98 is not the
initialization value, but the closure is executed, and the counter value is 95.
// add one, version five
function add_v5 (init_val) {
var innerNum = init_val;
function addOne () {
innerNum += 1;
return innerNum;
}
}
Here I have named the inner function, but the adder function does not return anything. That's why I'm not calling this one a closure - it's just a private function. This is a pitfall you need avoid when coding. You'll run into all sorts of errors if you don't return a value when you need to.
var g = add_v5(99);// undefined
g();//error - trying to execute a variable that is not a function
// add one, version six
function add_v6 (init_val) {
var innerNum = init_val;
return function addOne () {
innerNum += 1;
return innerNum;
}
}
This is the most useful version for this set of exercises.
var h = add_v6(101);
h();//102
h();//103
// add one, version seven
var add_v7 = function (init) {
this.counter = init,
function () {
counter += 1;
console.log("counter is " + counter);
return counter;
},
function () {
counter += 2;
console.log("counter is " + counter);
return counter;
}
};
An exercise left to the reader.