Related series: A Journey of Front-end Foundation from Scratch

One important concept to understand before you understand closures is the JS execution context.

Every time the engine encounters a function call, it creates a new execution context for that function and pushes it to the top of the stack.

Three things happen during the creation phase:

  1. This binding.
  2. Create the lexical environment component.
  3. Create the variable environment component.

When the function completes execution, the execution context pops out of the stack and the control flow moves to the next context in the current stack. All variables declared in the execution context are deleted.

Let’s take a look:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope(); // local scope
Copy the code

According to the lexical scope logic, when looking for a variable, it first looks for the variable object of the current context. If not, it looks for the variable object of the execution context from the parent (lexical level parent) until it finds the variable object of the global context.

Therefore, when the inner function F is executed, it finds the scope in the checkScope of the parent function that defines the position and returns it, and then the context of the inner function and the outer function are popped up and destroyed successively.

Here’s another chestnut:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
const f2 = checkscope();
f2(); // local scope
Copy the code

Instead of calling f inside the function, f returns an assignment to F2, so with the checkScope function ending, f is not destroyed because F2 holds a reference to f, and scope is not destroyed because F holds the scope of the external function.

It is concluded that:

  • Closures are internal functions, and we can do this by either inside a function or{}Block defines a function to create the closure. Closures can access an external scope even if the external scope is finished executing.
  • Closures are destroyed at the end of execution without being used externally
  • The outer scope of a closure is determined when it is defined, not when it is executed.

Here’s the extended reading (lazy direct copy, link at the end, guilty ~) :

Closures and loops

Closures only store references to external variables and do not copy their values.

function initEvents(){
  for(var i=1; i<=3; i++){
    setTimeout(function showNumber(){
     console.log(i)
    },10);
  }
}
initEvents(); // 4,4,4
Copy the code

In this example, we created three closures that all reference the same variable I, and since variable I increments with the loop, the final output is the same.

Using let variable declarations in a for block creates a new local variable for the FOR block each time through the loop.

function initEvents(){
  for(let i=1; i<=3; i++){
    setTimeout(function showNumber(){
     console.log(i)
    },10);
  }
}
initEvents();
Copy the code

Functions and private states

We can create functions that have private state with closures, which allow the state to be encapsulated.

Autoproliferator function

With closures, we can create autoproliferator functions. Again, the internal state is private. The following is an example:

function createAGenerate(count, increment) {
  return function(){
    count += increment;
    returncount; }}let generateNextNumber = createAGenerate(0.1);
console.log(generateNextNumber()); / / 1
console.log(generateNextNumber()); / / 2
console.log(generateNextNumber()); / / 3
let generateMultipleOfTen = createAGenerate(0.10);
console.log(generateMultipleOfTen()); / / 10
console.log(generateMultipleOfTen()); / / 20
console.log(generateMultipleOfTen()); / / 30
Copy the code

Object and private state

In the example above, we can create a function that has a private state. We can also create multiple functions that have the same private state. Based on this, we can also create an object that has a private state.

function TodoStore(){
  let todos = [];
  
  function add(todo){
    todos.push(todo);
  }
  function get(){
    return todos.map(toTodoViewModel);
  }
  
  function toTodoViewModel(todo) {
     return { id : todo.id, title : todo.title };
  }
  
  return Object.freeze({ add, get }); } Duplicate codeCopy the code

The TodoStore() function returns an object with a private state. Externally, we have no access to private ToDOS variables, and the add and GET closures have the same private state. In this case, TodoStore() is a factory function.

Closures vs pure functions

Closures are functions that refer to variables in an external scope.

To better understand, we split the inner functions into closures and pure functions:

  • Closures are functions that refer to variables in an external scope.
  • Pure functions are those that do not refer to variables in an external scope; they usually return a value and have no side effects.

In the example above, the Add () and get() functions are closures, while isPriorityTodo() and toTodoViewModel() are pure functions.

Decorator functions also make use of closures.

The garbage collection

In Javascript, local variables are destroyed as the function completes execution, unless there is a reference to them. When the closures themselves are garbage collected, the private states in those closures are subsequently garbage collected. We can usually do this by cutting off references to closures.

Avoid global variables

Global variables are easy to create in Javascript. Any variables defined outside functions and {} blocks are global, as are functions defined in global scope.

If you learn something new, or get a nice picture on the left, please give it a thumbs up

Reference article:

Discover the power of closures in JavaScript