JavaScript: in depth practical explanation on closures.

A deep look at JavaScript closures with explanations and hands-on code examples.

JavaScript Logo
JavaScript Logo

Introduction

A common struggle among JavaScrip novices is the understanding of closures. Actually they are quite easy to understand, they are all around the JavaScript code as they are an implementation detail of the lexical scope specification.

Lexical Scope and the Funarg problem emoji 🤔

The Lexical Scope is an abstract way (in JavaScript implemented using closures) to deal with free variable resolution and the Funarg problem.

Free variables are the ones not directly declared inside the function scope. When receiving and returning functions from other functions I (the interpreter) need a way to resolve those variables. The problem basically is like: "I'm a function, where can I resolve free variables? In the outer scope where I'm declared or in my caller's scope?".

The JavaScript language has opted to resolve this problem using the variables declared in the function's declaration outer scope(s).

//Free variables.
var x = 5; // Free variable in respect to `f`.

function f(){

	var y = 5;
	return x * y; // Where do I find `x`?
	
}
f(); // 25.

//Free variables, which one?.
function g(fn){

	var x = 10;
	return fn(); // Do I have to take the `x = 5` in the outer declaration scope or the `x = 10` in the caller scope?
	
}
g(f); // Still 25! JavaScript resolves free variables in its outer declaration scope.

The lookup process, introducing the Scope Chain emoji 🔗

Every time the JavaScript engine enter a new function declaration, it creates an object representing its scope. If inside that scope you create a new function, a new object is created as well for the newly created function to represent its scope, and it holds a reference to its parent's scope object too.

//The scope chain.
function fOuter(){ // F_O_Scope_OBJ: {..., __parent_scope__: GLOBAL_SCOPE, x: 5}

	var x = 5;
	
	return function fInner(){ // F_I_Scope_OBJ: {..., __parent_scope__: F_O_Scope_OBJ, y: 10}
		var y = 10;
		return x * y;
   	}
	
}

var x = 10;
var innerFn = fOuter();
innerFn(); // 50 and not 100!

// emoji 👉 The lookup process in detail:
// --> return x * y;
// --> return x * 10;
// --> resolve x:
// -->     F_I_Scope_OBJ['x'] exists? No, search in the __parent_scope__.
// -->     The __parent_scope__ is F_O_Scope_OBJ.
// -->     F_O_Scope_OBJ['x'] exists? Yes, return it (See [1]).
// --> return 5 * 10;
// --> return 50;

Why doesn't this code work?! emoji 😡

A variant of the code below is really really usual among Front-end code interviews. At this point you should be able to easily solve it yourself, but let's review it anyway. emoji 😉

//The for loop with weird callbacks.
for( var i=0; i < 3; i++ ){
	setTimeout(function(){
		console.log(i);
	}, i * 500);
}

The problem: "I expected that code to log 0 | 1 | 2, but I got 3 | 3 | 3, what's wrong with it?". emoji 😨

The answer: "The variable i is resolved in the outer scope of the function declaration, and thus when the callback is invoked the i value is 3, which is the condition when the loop exited.". emoji 😎

The solution: create a new closure to "freeze" the value using and IIFE (Immediately Invoked Function Expression), use let to declare the i variable in a block-scoped way (see [2]), or set its first argument as a default value (not reference!) using the bind helper.

//The for loop with weird callbacks, solutions.

//Create a new closure. emoji 🎉
for( var i=0; i < 3; i++ ){
	setTimeout((function(x) {
		return function(){
			console.log(x);
		}
	})(i), i * 500);
}

//Use let. emoji 🎉
for( let i=0; i < 3; i++ ){
	setTimeout(function(){
		console.log(i);
	}, i * 500);
}

//Use bind. emoji 🎉
for( var i=0; i < 3; i++ ){
	setTimeout(console.log.bind(null, i), i * 500);
}

emoji 📔 Footnotes

[1] If it is not found in its parent scope, the engine will search in its parent's parent too! This all the way up to the Global Scope.

[2] Discover more on the differences and gotchas between var, let and const.

emoji 📚 Further readings

The Funarg problem

How do JavaScript closures work?

The bind helper

About an Immediately Invoked Function Expression

For updates, insights or suggestions, feel free to post a comment below! emoji 🙂

×