O'Reilly logo

JavaScript Cookbook by Shelley Powers

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

6.7. Create a Function That Remembers Its State

Problem

You want to create a function that can remember static data, but without having to use global variables and without resending the same data with each function call.

Solution

Create an outer function that takes one or more parameters, and then an inner function that also takes one or more parameters but uses both its and its parent function’s parameters in order to perform its functionality. Return the inner function from the outer function, and assign it to a variable. From that point, use the variable as a function:

function greetingMaker(greeting) {
   function addName(name) {
      return greeting + " " + name;
   }
   return addName;
}

// Now, create new partial functions
var daytimeGreeting = greetingMaker("Good Day to you");
var nightGreeting = greetingMaker("Good Evening");

...

// if daytime
alert(daytimeGreeting(name));

// if night
alert(nightGreeting(name));

Discussion

We want to avoid global variables as much as possible because of potential clashes between libraries. However, there are times when you need to store data to be used across several function calls, and you don’t want to have to repeatedly send this information to the function each time.

A way to persist this data from one function to another other is to create one of the functions within the other, so both have access to the data, and then return the inner function from the outer. Returning one function from another is known as a function closure. Before I get into the specifics of function closure, I want to spend a few minutes on functions and scope.

Note

This type of function closure is also known as a partial function, or currying, which is covered in detail in Recipe 6.8.

In the solution, the inner function addName is defined in the outer function greetingMaker. Both of the functions have one argument. The inner function has access to both its argument and the outer function’s argument, but the outer function cannot access the argument passed to the inner function. The inner function can operate on the outer function’s parameters because it is operating within the same context, or scope, of the outer function.

In JavaScript, there is one scope that is created for the outermost application environment. All global variables, functions, and objects are contained within this outer scope.

When you create a function, you create a new scope that exists as long as the function exists. The function has access to all variables in its scope, as well as all of the variables from the outer scope, but the outer scope does not have access to the variables in the function. Because of these scoping rules, we can access window and document objects in all of our browser applications, and the inner function in the solution can also access the data passed to, or originating in, the outer function that wraps it.

Note

This also explains how the recursive functions in Recipe 6.6 can internally access the variables they’re assigned to in the outer application scope.

However, the outer function cannot access the inner function’s arguments or local data, because they exist in a different scope.

An inner function doesn’t have to be returned from the outer function. It could be an invoked direction in the code in the outer function. When it is returned, like in the solution, and the following code:

function outer (x) {
  return function(y) { return x * y; };
}

var multiThree = outer(3);
alert(multiThree(2)); // 6 is printed
alert(multiThree(3)); // 9 is printed

the returned function forms a closure. A JavaScript closure is a variable local to a function that remains alive when the function returns.

When the inner function is returned from the outer function, its application scope at the time, including all references to the outer function’s variables, is now passed to the outer, global scope. So even though the outer function’s application scope no longer exists, the inner function’s scope exists at the time the function was returned, including a snapshot of the outer function’s data. It will continue to exist until the application is finished, and the outer, global scope is released.

Note

Another way a closure can be made is if an inner function is assigned to a global variable.

So what happens to these variables when an application scope is released? JavaScript supports automatic garbage collection. What this means is you and I don’t have to manually allocate or deallocate memory for our variables. Instead, the memory for variables is created automatically when we create variables and objects, and deallocated automatically when the variable scope is released.

In the solution, the outer function greetingMaker takes one argument, which is a specific greeting. It also returns an inner function, addName, which itself takes the person’s name. In the code, greetingMaker is called twice, once with a daytime greeting, assigned to a variable called daytimeGreeting, and once with a nighttime greeting, assigned to a variable called nightGreeting.

Now, whenever we want to greet someone in daytime, we can use the daytime greeting function, daytimeGreeting, passing in the name of the person. The same applies to the nighttime greeting function, nightGreeting. No matter how many times each is used, the greeting string doesn’t need to be respecified: we just pass in a different name. The specialized variations of the greeting remain in scope until the page that contains the application is unloaded from the browser.

Closures are interesting and useful, especially when working with JavaScript objects, as we’ll see later in the book. But there is a downside to closures that turns up when we create accidental closures.

An accidental closure occurs when we code JavaScript that creates closures, but aren’t aware that we’ve done so. Each closure takes up memory, and the more closures we create, the more memory is used. The problem is compounded if the memory isn’t released when the application scope is released. When this happens, the result is a persistent memory leak.

Here’s an example of an accidental closure:

function outerFunction() {
   var doc = document.getElementById("doc");
   var newObj = { 'doc' : doc};
   doc.newObj = newObj;
}

The newObj contains one property, doc, which contains a reference to the page element identified by doc. But then, this element is given a new property, newObj, which contains a reference to the new object you just created, which in turn contains a reference to the page element. This is a circular reference from object to page element, and page element to object.

The problem with this circular reference is exacerbated in earlier versions of IE, because IE does not release memory associated with DOM objects (such as the doc element) if application scope is released. Even leaving the page does not reclaim the memory: you have to close the browser.

Note

Other browsers detected this type of situation and performed a cleanup when the user left the application (the web page where the JavaScript resided).

Luckily, newer versions of IE don’t have this problem. However, function closures should be deliberate, rather than accidental.

See Also

More on function closures and private function methods in Chapter 16. An excellent overview of function scope and closure is “JavaScript Closures”, by Richard Cornford. Though written in 2004, it’s still one of the best descriptions of closures. Mozilla provides a nice, clean description of closures at https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Working_with_Closures.

Recipe 6.8 also makes use of specialized function closure, specifically for simplifying the number of arguments that have to be passed to functions, an effect called currying.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required