JavaScript Closures: a Newbie's First Coding Headache

| Comments

In JavaScript, local variables tend to exist for as long as the function block they’re defined in is executing. The thing about nested functions is they can be used to exceed that life expectancy tremendously.

If you’re unfamiliar with JavaScript’s execution context, we discuss it here. Each time a function is called / activated, JavaScript creates a new execution context with a new special object - the call / activation object - to store the function’s arguments and local variables. This is how JavaScript creates the degrees of separation called scope.

Scope determines access to and the visibility of code. In JavaScript, code has either global scope - everything has access to it - or local / function scope.

Location, Location, Location

When code exists within a function it has local scope and is hidden from the code contained without. Working in the other direction, local code has access to the code in its outer scope.

This long reach is achieved because a function’s execution context has associated with it a scope chain - a string of one or (because a function can call a function) more activation object(s) stretching back to the global object. If JavaScript cannot find what it’s looking for in the executing function’s activation object, it checks the next object in the scope chain and so on, until, if it has not already found the missing value, it arrives at the global object and checks there.

Most of the time, local scopes exist only temporarily; once a function has exited, its activation object is no longer accessible to in-scope code, so JavaScript reclaims the memory it uses. But, if a reference to a nested function is caught in another scope, it takes with it access to the environment in which it was defined. This phenomenon is known as a closure:

JavaScript functions are a combination of code to be executed and the scope in which to execute them. This combination of code and scope is known as a closure in the computer science literature. All JavaScript functions are closures. These closures are only interesting, however… when a nested function is exported outside the scope in which it is defined. When a nested function is used in this way, it is often explicitly called a closure.

JavaScript, The Definitive Guide, 8.8.4, by David Flanagan

Example - Bob Rules

Bob is my niece. She’s happy to share her corn snacks one at a time, but, and this is crucial if you don’t want to cross Bob, nobody else gets direct access to the packet, so it must absolutely not be left lying about in the public interface. Here’s Bob’s code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function packetCornSnacks() {

  var cornSnackCount = 3;

  function bobShares() {
    if(cornSnackCount > 0){
      cornSnackCount-=1;
      return "One corn snack for you!";
    } else {
      return "Oops, my bag is empty!";
    }
  }
  return bobShares;
}
// Save ref to nested function (bobShares) in global
var getSnacks = packetCornSnacks();

// Invoke bobShares...
alert(getSnacks()); // One corn snack for you!
alert(getSnacks()); // One corn snack for you!
alert(getSnacks()); // One corn snack for you!
alert(getSnacks()); // Oops, my bag is empty!

If you run this code, you’ll ‘get’ one corn snack until Bob’s bag is empty. Nobody can go in and change the value of cornSnackCount - i.e., nab all the virtual corn snacks on the sly - because cornSnackCount is hidden inside a function. Bob is happy!

Happy But Confused

It mightn’t be immediately obvious how we achieve this state of corn snack affairs, so let’s talk it through…

In the above example, the outer function (packetCornSnacks) returns its inner function (bobShares), and the code at the bottom saves a reference to this nested function in the global environment, changing the normal flow of things. The nested function is created when its outer function is invoked, and it contains an inherent reference to where it was defined. So, even when the outer function has finished executing, a reference to its activation object remains in the global scope. The variable cornSnackCount persists in this object.

The code at the very bottom calls bobShares multiple times. Each time it is called, the function is the same, but its scope is changed because the value of cornSnackCount defined in the outer function is decremented over invocations, until Bob’s bag is empty.

Conclusion

getSnacks is a closure of the interesting variety. Bob’s packet of corn snacks can’t be interfered with, but its value is remembered across invocations of bobShares.

This has been a particularly tricky topic to approach as a newbie coder, and I’d welcome any pointers that could provide further insight.

Comments