Skip to content

Instantly share code, notes, and snippets.

@yhay81
Last active March 9, 2018 13:06
Show Gist options
  • Save yhay81/4e916026f72a569638c9258351b28d4c to your computer and use it in GitHub Desktop.
Save yhay81/4e916026f72a569638c9258351b28d4c to your computer and use it in GitHub Desktop.

5 Steps to know about JavaScript Closure

This is what I think is easy way to understand what closure of JavaScript is.

Step 1. Declaration variables

When we use variables in programming, at first we declare it and then use it. So, what happens when I declare the variable which name is already declared?

const foo = 0;
const foo = 1;  // This raise `SyntaxError: Identifier 'foo' has already been declared`.

var bar = 0;
var bar = 1;  // This overwrites the previous one.

Of course, we cannot use same name variables. But then, how to do with too big programs? There may be many variables and it is hard to care anytime not to use already declared names.

So, we can use Scope. The scope is a region where declarations of variables effect.

Step 2. Scope

At first, let us take a look at the example of scope.

let foo = "outer foo";
let bar = "outer bar";

{
    let foo = "inner foo";

    console.log(foo);  // "inner foo"
    console.log(bar);  // "outer bar"

    foo = "inner assignment of foo"
    bar = "inner assignment of bar"
}

console.log(foo);  // "outer foo"
console.log(bar);  // "inner assignment of bar"

Speaking of variables declared by let or const, curly brackets make scopes. If we declare a variable in a curly bracket, we can declare it even if we already declare it on the outside of the scope of the bracket.

When we use a variable, if it has been declared in the scope, the variable is the one which declared in the scope. And only when it has not been declared in the scope, the variable is the one which declared on the outside of the bracket. This follows the rule:

"Look for the variable in the scope, and if there are not, look for outer".

We can not only read the outer variables also assign values to them.

Note: var and curly brackets

var declaration is not the same as let and const in terms of scope. The scope of variables with var declaration is only inside of the function, and curly brackets are ignored.

var foo = "outer";

{
    var foo = "inner curly bracket";  // This overwrite the varuable declaration.
    const func = function(){
        var foo = "inner function";
    };
}

console.log(foo);  // "inner curly bracket"

Function declaration behaves same as this. Function declaration and var are from ES5, and let and const are from ES6 feature.

Step 3. Scope of functions

Functions also make scopes as we can see it by that functions are declared with curly brackets. But there may be some more ambiguous points about scopes if the scope is made as functions because they repeatedly do the statement in them.

Take a look at this example:

const func = function (){
    const foo = 0;
    console.log(foo);
};

func();  // 0
func();  // 0 :Here occurs no error.

Variables which is declared in the function do not be shared with executions each other. Each execution makes another scope.

Next, what do you think about the case if the declaration is out of the function as below? :

const foo = "assignment over the DECLARATION";
const func = function (){
    console.log(foo);
};

func();  // "assignment over the DECLARATION"

{
    const foo = "assignment over the EXECUTION";
    func();  // Which?
}

This code follows the rule which we already see : "Look for the variable in the scope, and if there are not, look for out of the scope". But we cannot predict what the code output is only from the rule, because in this case, "out of the scope" is ambiguous. We have to think about where is the "out" and we can imagine two possibilities.

  1. "out" means the place which the function declared.
  2. "out" means the place which the function executed.

The former pattern is called dynamic scope and the latter pattern is called static scope or lexical scope. JavaScript adopts lexical scope, so the code output is "assignment over the DECLARATION".

Step 4. Closure

So how about the similar case below?:

let foo = "the first assignment";
const func = function (){
    console.log(foo);
};

foo = "the second assignment";
func();  // Which?

Do you think like that? : "When the function declared, the value of foo is "the first assignment". So the output is "the first assignment"."

It is not true. This code says "the second assignment". Functions keep the variables from the outer scope as a variable as it is, not as a copy. So, if the value of the variable changes, the result also changes even if the change is made after the declaration of the function.

Thus, Of course, that value can be changed from the inside of the function and the effect extends to the variable of outside.

let count = 0;
const countUp = function (){
    count++;
    console.log(count);
};

countUp();  // 1
console.log(count);  // 1

These functions, which keep the variables from outer scope: lexical variables, are called the Closure.

Note: Life time of variables in function

This note is for those who care about memory or garbage collection.

Normally, once the execution is finished the value declared in a function is thrown away from memory.

const func = function(){
    let count = 0;
    console.log(count);
};

func();  // 0
// Here, the `count` used in `func` have already disapeared from memory.

But when we use closures, even if the function which makes the outer scope seems to disappear, the variables do not disappear because closure keeps the outer variables if it is used in the closure.

const func = function(){
    let count = 0;
    const innerFunc = function (){
        count++;
        console.log(count);
    };
    return innerFunc;
};

const outerFunc = func();
outerFunc();  // 1
outerFunc();  // 2

Step 5. Typical patterns of Closure

Closures are all the same in the point of view that they keep variables from the outer scope. But they can do so various things that we may feel they are not so similar to call them in the same name. This may make beginners feel closures are difficult. So I think knowing typical and classified patterns of closures usage is the good way to be familiar with closures. While there are more usages of closures, I will show 5 basic patterns. They are simple but we can find many practical usages in it with a little imagination.

Having the state

As we see before in countUp, we can keep the state of the inside of closures.

let count = 0;
const doOnlyOnce = function (){
    if(count==0){
        console.log("You do this for first time.");
    }else{
        console.log("You already have done this.");
    }
    count++;
};

doOnlyOnce();  // "You do this for first time."
doOnlyOnce();  // "You already have done this."
doOnlyOnce();  // "You already have done this."

In many practical cases, the outer scope of closure is function and the function returns closures or objects containing closures. This example may become better by creating the closure from a function because this does not pollute outer scope and can be used repeatedly like below:

const createFunc = function(){
    let count = 0;
    const doOnlyOnce = function (){
        if(count==0){
            console.log("You do this for first time.");
        }else{
            console.log("You already have done this.");
        }
        count++;
    };
    return doOnlyOnce;
};

const myDoOnlyOnce = createFunc();
const anotherDoOnlyOnce = createFunc();

myDoOnlyOnce();  // "You do this for first time."
myDoOnlyOnce();  // "You already have done this."
anotherDoOnlyOnce();  // "You do this for first time."

Examples below are all such that return closure function from a function.

Functions sharing the state with other functions

It is important to know that we can create some closures which share same variables.

const createFuncs = function(){
    let innerVariable = 0;
    const countUp = function(value){
        innerVariable++;
    };
    const show = function(){
        console.log(innerVariable);
    };
    return [countUp, show];
};

const [myCountUp, myShow] = createFuncs();
const [anotherCountUp, anotherShow] = createFuncs();

myCountUp();
myShow();  // 1
anotherShow();  // 0

This innerVariable is a value which acts like as a private variable frequently used in object-oriented programming. And we can change the example more like OOP as below.

const construct = function(){
    let innerVariable = 0;
    const countUp = function(value){
        innerVariable++;
    };
    const show = function(){
        console.log(innerVariable);
    };
    return {countup, show};
};

const myObj = construct();

myObj.countUp();
myObj.show(); // 1

Get function with already option set

When we make codes, we often create functions with some arguments and some of them are almost always the same when we use it. Then we can create new functions which already have been set the value of arguments as a closure.

// This is a example of normal function.
const exponential = function(base, exponent){
    return base ** exponent;
}
console.log(exponential(8, 2));  // 64

// We may want to use it with always base being 2.
// So we can do it like this.

const powerOf = function(exponent){
    const pow = function(base){
        return base ** exponent;
    };
    return pow;
};

const square = powerOf(2);
const cube = powerOf(3);

console.log(square(8));  // 64
console.log(square(32));  // 1024
console.log(cube(8));  // 512

Do something with preprocessed value

Sometimes we want to use arguments always after we apply some preprocess to them. Closures make it possible to separate preprocess and the main algorism. Then it becomes easy to change the preprocess.

const greeting = function(preprocessFunc){
    const output = function(name){
        const nameDecolated = preprocessFunc(name);
        console.log(nameDecolated);
    };
    return output;
};

const preprocess1 = function(name){
    return "Hello, " + name + "!";
};

const preprocess2 = function(name){
    return "Good morning, " + name + " :)";
};

const hello = greeting(preprocess1);
const mornin = greeting(preprocess2);

hello("Yusuke");  // Hello, Yusuke!
mornin("Yusuke");  // Good morning, Yusuke :)

Do something before and/or after execution

This Pattern is typical, like the example, for measuring how long the execution takes.

const withTimeMesuring = function(funcToExecute){
    const func = function(arg){
        console.time('timer');
        console.log("***start of execution***");
        funcToExecute(arg);
        console.log("***end of execution***");
        console.timeEnd('timer');
    };
    return func;
};

const incrementManyTimes = function(times){
    count = 0
    for(let i=0;i<times;i++){
        count++;
    }
    console.log("Done!")
};

const myFunc = withTimeMesuring(incrementManyTimes);

myFunc(1000000);
// ***start of execution***
// Done!
// ***end of execution***
// timer: 4.391845703125ms
const foo = 0;
const foo = 1; // This raise `SyntaxError: Identifier 'foo' has already been declared`.
var bar = 0;
var bar = 1; // This overwrites the previous one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment