Scope And Closures In Javascript

Scope And Closures In Javascript

Jarkyn Soltobaeva

June 13, 2017

To understand closures and their use in JavaScript, it is essential to understand that closures rely on the scope mechanism. So, before we jump into closures, let's remind ourselves what scope is and how it works in JavaScript.

What is scope?

Imagine if the only way to organise your files and applications on your computer was to keep them all on your desktop... That would not be very helpful when you needed to find one particular file. We are used to creating folders and sub-folders based on some common theme so that we can group related files in those folders/sub-folders. We use a similar approach for organising our code to help us reason about it and not be overwhelmed. Plus, we don't need to have access to all functions and variables at once to make our programs work. So scope is very much about which variables (or functions) are visible (i.e. available) to other parts of code at any given moment. The value of the variable during the execution flow will depend on the location of the variable declaration and where it is being accessed in relation to its declaration.

Lexical vs dynamic

  • With lexical scoping, also called “static”, the structure of the program source code determines the values of the variables you are referring to.
  • With dynamic scoping, the runtime state of the program stack determines what variable you are referring to.

JavaScript has lexical scope. This is a common way the scope is implemented in most languages. I suspect this is because it is easier to reason about, although some may disagree.

Scope in JavaScript

Creation of scope

Before ECMAScript 6, only functions created new scope—there was no block scope. This just means that curly braces don't create scope—i.e. anything defined inside if, for, and while is not only accessible inside that block, but inside the parent scope as well. Since scope is lexical, functions create their scope (or their environment) when they are defined, not when they are executed.

Levels of scope

JavaScript has two levels of scope: global and local. Anything declared outside of any function is defined on global scope. Even if you declare a variable inside a function but do not use var keyword, you also define a global variable. It is called global because it is available to all parts of the code, and therefore to all local scopes. Let's go back to the desktop example. Imagine the desktop is our global scope. When we create a folder on the desktop, the desktop becomes a parent and the folder its child. The folder has its own local scope, and any files we create there are not visible to the desktop itself. The same holds true when we create a sub-folder inside the folder, only in this case the original folder becomes the parent. Similarly, in JavaScript, the parent (or global) scope does not have access to the children's (functions') scopes. However, child scope does have access to its parent's scope (this is where file system analogy diverges). This means that if a variable you are looking for is not found in a local scope, it will be looked up in the parent. JavaScript won't only look in the parent, it will keep going to the parent of the parent, and all the way to the global scope until it finds the variable we are after. To sum this up quickly—every function has its own local scope and any variable defined inside the function is available within that function and within any nested functions. This ability of a child scope to have access to its parent's scope is referred to as a scope chain.

Example

You should now be familiar enough with the terminology to look at some examples. I would like to note that in this post I will be using ECMAScript 5 syntax. The fundamental principles demonstrated in this post still apply even if you are using ECMAScript 6.

function f1(){
		var a = 1;
		return f2();
}

function f2(){
		return a;
}

f1();
// Uncaught ReferenceError: a is not defined

One might expect that because f2 is being invoked immediately after the declaration of a, the result of calling f1 which calls f2 should return the value of a. f2, just like f1, has access to their local scope and the global scope. However, at the time when f2 was defined, there was no a in sight, and f1 and f2 don’t share their local scopes. Hence, calling f1 results in the error.

When a function is defined, it remembers its environment—its scope chain. Also, notice how f1 and f2 are sort of siblings—they have the same parent. f2 is positioned after f1, yet f1 is calling f2. This works because all f1 needs is to remember its scope chain, and everything that shows up in its scope chain is immediately available to it.

Inner functions

You may have heard some say that JavaScript is a simple language. I think what they mean is that it does not have as many constructs as other languages, so it may seem easy to learn the language. But this does not make it easier to understand than other languages. Due to its minimalism, JavaScript uses functions to fulfill additional purposes. It is very common to nest functions, which provides several benefits, including:

  • keeping global namespace unpolluted (smaller chances of naming collisions)
  • allowing for privacy—i.e. we expose only the functions we choose to the “outside world”

Closures

Essentially, a closure is a structure composed of nested functions in such a way that allows us to break through the scope chain. By breaking the scope chain, closures allow access to data within inner scope that would otherwise be inaccessible by outer scope.

Let's attempt to visualise this. Imagine a folder structure. The outermost folder is G, aka global scope. Inside it we have a folder A, aka function. Inside A we have a file—a.txt, aka local variable.

G
|
|__ A
| |
| |__ a.txt
| |
| |__ B
| |
| |__ b.txt
|
|__ C
|
|__ c.txt

If we apply rules of lexical scope to our visualisation, folder G can see A and C, but it cannot see their contents. A can see what's in G's scope, as well as what's in its local scope: a.txt and B. In its simplest use case, a closure can break scope chain and allow access to the a.txt from the scope of G. This is the power of closures. Let's look at how this is actually implemented in JavaScript.

Closure 1


function f() {
		var b = 'b';
		return function() {
				return b;
		}
}

If we attempt to evaluate b outside of the function f, it will be undefined. However, the trick here is in the fact that f returns another function. We can assign this return value to a variable in the outer scope like so:

var n = f();

and then call it:

n();
>> 'b'

Voila! From the outer scope, we managed to access internal scope's data by returning a function that had access to it. Closure is created when a function (our internal anonymous function in this case) keeps a link to its parent scope, even after parent (b) has returned.

Closure 2

The result in the following example will be the same, however the mechanics are a tiny bit different:


var n; // we declare a variable on the outer scope beforehand
function f() {
		var b = 'b';
		n = function() { // and then, we assign to that variable
				return b;
		}
}

f();
n();
>> 'b'

f does not return anything anymore, but we still need to call it to set things up. Calling n will produce the same result as before

Closure and lexical scope example


var n;
function f(arg) {
		n = function() {
				return arg;
		}
		arg++;
}

f(123);

n();
>> 124

This example may seem surprising at first, but remember: functions, including closures, keep reference to their environment, not the values within them. Hence, at the time we execute n, the value of arg that it returns has already been incremented, regardless of the fact that when we look at the code, the line where the variable is incremented is after we assigned the function that uses that variable.

Iterating with a closure

If you have a simple array you would like to iterate over, we can always use a loop. There may be occasions when the data structure you are iterating over is more complex, with different rules as to what the sequence of values is. We can wrap the complicated “who is next” logic into an anonymous and therefore private function. Then we could expose next() as an easy-to-use interface that we call when we need the consecutive value. In this example, let's go over an array for simplicity, but let's only take every other element:

function setup(x) { // this function will take an array
		var i = -2;
		return function() {
				i = i + 2; // here we decide what the iteration logic is
				return x[i]; // and then our inner function would return an element based on the logic
		}
}

var next = setup([1, 2, 3, 4, 5, 6, 7, 8, 9]); // here we store the returned function in a variable

next();
>> 1
next();
>> 3
next();
>> 5
next();
>> 7
next();
>> 9

We've got ourselves a conditional iterator. I cannot say that using closures this way is always advisable, but knowledge is power, you just have to use it responsibly!

Closure in a loop

Now let's loop three times. Each time we will create a new function that returns the loop sequence number. We can add new functions to an array, and we will return it in the end.

function f() {
				var result = []; // we set up an array that will contain our functions

				for (var i = 0; i < 3; i++) { // we loop three times
								result[i] = function() { // each time we add a function to the array
												return i; // each function returns the sequence number
								}
				}
				return result;
}

var functions = f();

functions[0]()
>> 3

functions[1]()
>> 3

functions[2]()
>> 3

If you expected this result, you fully understand lexical scope. Closures, being functions, don’t remember values, they only link/reference the variable and will return its current value at the time of call. After the loop has executed, the value of i is 3.

This example demonstrates that depending on a mutable state within a closure (or anywhere for that matter) can easily go wrong, and can be tricky to debug. Here's how we can make our example work, if we must use something like that at all:

function f() {
		var result = [];

		function makeClosure(value) {
				return function() {
						return value;
				}
		}

		for (var i = 0; i < 3; i++) {
				result[i] = makeClosure(i);
		}
		return result;
}

And this should work:

var functions = f();

functions[0]()
>> 0

functions[1]()
>> 1

functions[2]()
>> 2

Before reading on further, have a ponder as to why this worked... What have we done differently? Instead of just creating a function and adding it to the result, we are calling another function, makeClosure, which returns the function we are after. However, makeClosure takes the value of the iterator as an argument, which becomes its local variable and has a different value every time. So the innermost function, which is added to the final array, will have a return value corresponding to each iteration of the loop.

And just to complicate things even further, below has the same effect, only it is written using a self-invoking anonymous function:


function f() {
		var result = [];

		for (var i = 0; i < 3; i++) {
				result[i] = (function(x) {
						return function() {
								return x;
						}
				})(i);
		}
		return result;
}

We had a look at a few uses of closures—some are fairly straightforward, others are rather clever. For me, closure is an abstraction mechanism first and foremost. I use it to keep things private when necessary. This is especially useful because global scope is a default scope in JavaScript. Personally, I would avoid using closures for mutating state. (Why we want to avoid mutable state is a separate topic that I won't get into here.) Ultimately, functions are the backbone of JavaScript, and to take full advantage of them we need a solid understanding of scope and closures. I hope this post helps clarify these constructs, especially if you are early on in your discovery of JavaScript.

Resources:

- Object-Oriented JavaScript, Stoyan Stefanov