The simplest explanation of JS scope and closure

As a more functional programming styled language, JavaScript has a simpler variable scope than some other object-oriented styled language, like C# and Java.

1. Variable scope

Before ES6, we only have 2 scopes for variables: global scope and function scope. In ES6, we also have an extra scope: block scope.

const globalVar = 'globalVar'; // global scope
function func() {
    const funcVarA = 'funcVarA'; // func scope
    console.log(`${globalVar} ${funcVarA}`); 
}

console.log(funcVarA); // undefined
console.log(result); // undefined
func(); // 'globalVar funcVarA'

Very simple, right? It's similar with most other language:

  1. The function scope variables can't be accessed out of the function, but a global variable can be used by any functions
  2. in a nested functions structure, the child function has the access of variables in its parent function(s), but a parent function can't access the variables in any of its children functions.

Note: when func is invoked, the funcVarA will be collected from memory.

The question is, if my code is like this:

function func() {
    const funcVarA = [1,2,3]; // func scope
    return function childFunc() {
        let result = 0; // func scope
        for(let i=0; i < funcVarA.length; i++) { // i is in block scope
            result += funcVarA[i];
        }
        
        return result;
    }
}

const sum = func(); // func is already invoked
const result = sum();

console.log(result); // 6

My question is, the func is already invoked and return the function childFunc to sum, and when sum is invoked, we can get the correct result 6, which means in childFunc still can access the funcVarA from func even func was executed before.

This JavaScript feature is called CLOSURE.

2. Closure

Don't be scared be this naming, it's very simple: it's a feature of JavaScript that when you invoke a function, you can still access its outside-scope variables when it is defined, like a ***"closed bundle"***.

Free variable: a variable that is used in a function but is not local or parameters of that function.

Usually, closure will happen for two scenarios:

  1. function is returned as a result. We can simplify above code like this:

    function create() {
        const a = 100;
        return function () {
            console.log(a);
        }
    }
    const fn = create();
    const a = 200;
    fn();
    

    Q: What's the printing result?

    A: 100.

  2. function is passed as a parameter:

    function print(fn) {
        const a = 200;
        fn();
    }
    const a = 100;
    function fn() {
        console.log(a)
    }
    print(fn);
    

    Q: What's the printing result?

    A: 100.

The conclusion is very obvious: a is a free variable, with closure feature, complier will follow the scope chain to search a in the place where the function is defined, NOT where the function is invoked.