Execution context and scope

Before explaining closures, you need to understand the concepts of execution context and scope, which will help you understand closures later.

The context of variables or functions determines what data they can access. Each context has a variable object (scope) associated with it, and all variables and functions defined in the context are stored in this object. The code we write doesn’t have access to this object, but the parser uses it behind the scenes as it processes the data. There are two types of context: global context and each function’s own context. (Some articles will translate the context as the execution environment, essentially the same thing, “JavaScript Advanced Programming” third edition Chinese translation is the execution environment, the fourth edition Chinese translation is the execution context)

The global context is the outermost context. The objects representing the global context may differ depending on the host environment. In web browsers, the global context is what we call the Window object, so all global variables and functions defined by var become properties and methods of the Window object. Each function call has its own context. When code execution flows into a function, the context of the function is pushed onto a context stack. Context in all its will be destroyed upon completion of the code is executed, including all the variables and functions defined on it, for example: the function of context in the function after the execution, the context stack will pop up the function context and destruction of the global context will be destroyed before the application exits, such as closing web page or application exit.

When the code in the context executes, it creates a chain of scopes. This chain of scopes determines the order in which the code at each level of context accesses variables and functions. The variable object (scope) of the context in which the code is executing is always at the top of the scope chain. The next variable object (scope) in the scope chain comes from the containing context, and the next object comes from the next containing context. And so on up to the global context; The variable object of the global context is always the last variable object in the scope chain. Code execution is done by searching down the scope chain. The search process always starts at the very top of the scope chain and moves down the chain until variables are found.

Here’s an example:

var a = 1
function fn1() {
    var b = 2;
    function fn2() {
        var c = 3;
        console.log(a); / / 1.
    }
    fn2();
}
fn1();
Copy the code

The following is explained step by step in the order in which the code is executed:

  1. Create a global context and push the global context onto the context stack.
  2. Create variable A in the global context and assign the value 1.
  3. On lines 2-9, declare a new variable fn1 in the global context and assign a function definition.
  4. Find the variable fn1 from the scope chain created by the global context and execute. Note: The scope chain of the global context is only: the variable object of the global context (global scope).
  5. Fn1 function call execution, create a fN1 function execution context, and push the context stack. Note: Now context stack: function fn1 context -> global context.
  6. Create variable b in the context of the fn1 function and assign it a value of 2.
  7. In lines 4-7, create the variable fn2 in the context of the fn1 function and assign a function definition.
  8. Find the variable fn2 from the scope chain created by the fn1 function context and execute. Note: the scope chain created by function fn1 is: variable object of function fn1 context (local scope) -> variable object of global context (global scope).
  9. Function fn2 call execution, create an fn2 function execution context, and push the context stack. Note: Context stack now: function fn2 context -> function fn1 context -> global context.
  10. Create variable c in the context of function fn2 and assign 3.
  11. Find variable A from the scope chain created by the fn2 function context, and console. Note: the scope chain created by function fn2 is: variable objects in the context of function fn2 (local scope) -> variable objects in the context of function Fn1 (local scope) -> variable objects in the global context (global scope). The variable a is accessible in the fn2 function through the scope chain.
  12. The fn2 context pops out of the context stack and is destroyed. Note: Now context stack: function fn1 context -> global context.
  13. The fn1 context pops out of the context stack and is destroyed. Note: Context stack now: global context.

What is a closure

Official definition: Closures are functions that refer to variables in the scope of another function, usually implemented in nested functions.

Function B carries all of the variables in function A’s scope and returns them with it. The chain of scopes at which B functions execute becomes the scope of B functions -> the scope (closure) of A functions -> other -> global scope, so that variables in A functions can be accessed in B functions.

Here’s an example:

function fn1() {
    var b = 2;
    function fn2() {
        var c = 3;
        console.log(b); / / 2;
    }
    return fn2;
}
var a = fn1();
a();
Copy the code

The following is explained step by step in the order in which the code is executed:

  1. Create a global context and push the global context onto the context stack.
  2. On lines 1-8, declare a new variable, fn1, in the global context and assign a function definition.
  3. Create variable A in the global context and assign the result of fn1 execution to A.
  4. Fn1 function call execution, create a fN1 function execution context, and push the context stack. Note: Now context stack: function fn1 context -> global context.
  5. Create variable b in the context of the fn1 function and assign it a value of 2.
  6. In lines 3-6, the fn1 function context declares a new variable, fn2, and assigns a function definition. In addition, we will create a closure that contains all the variables in fn1 as part of the function definition.
  7. Find the variable fn2 from the scope chain created by the fn1 function context and return fn2 along with the closure (the variables used in fn1). Note: the scope chain created by function fn1 is: variable object of function fn1 context (local scope) -> variable object of global context (global scope).
  8. The fn1 context pops out of the context stack and is destroyed. Note: The context stack now only has: global context.
  9. Execute the a function, which is called to execute fn2.
  10. Fn2 function call execution, create an fn2 function execution context, and push the context stack. Note: Now context stack: function fn2 context -> global context.
  11. Create the variable c in the context of the fn2 function and assign it to 3.
  12. Find variable B from the scope chain created by the fn2 function context, and console. Note: the scope chain created by function fn2 is: variable objects in the context of function fn2 (local scope) -> variable objects in the context of function Fn1 (closure) -> variable objects in the global context (global scope). The variable b that is accessible in the fn2 function is found in the closure through the scope chain.
  13. The fn2 context pops out of the context stack and is destroyed. Note: Context stack now: global context.

Closure applications: Hide data and provide only apis

function createCache() {
    const data = {} // The data in the closure is hidden from outside access
    return {
        set: function (key, val) {
            data[key] = val
        },
        get: function (key) {
            return data[key]
        }
    }
}

const c = createCache()
c.set('a'.100)
console.log( c.get('a'))Copy the code

Disadvantages of closures

Because closures retain the scope of their containing functions, they take up more memory than other functions. Overuse of closures can lead to overuse of memory, so use them only when absolutely necessary is recommended. Optimized JavaScript engines like V8 try to reclaim memory trapped by closures, but we recommend caution when using closures.

Closures are common

// The basic closure
function create() {
    const a = 100
    return function () {
        console.log(a)
    }
}

const fn = create()
const a = 200
fn() / / 100
Copy the code
// This is not a closure, this is a function as an argument
function print(fn) {
    const a = 200
    fn()
}
const a = 100
function fn() {
    console.log(a)
}
print(fn) / / 100
Copy the code
// The closure produced by multiple executions is also the same live object in memory
function createCounter() {
    let counter = 0
    const myFunction = function() {
        counter = counter + 1
        return counter
    }
    return myFunction
}
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('example increment', c1, c2, c3) //example increment 1 2 3
Copy the code
function createFunctions(){
    var result = new Array(a);for (var i = 0; i < 10; i++) {
        result[i] = function(){
            return i;
        };
    }
    return result;
}
var res = createFunctions();
res[0] ();/ / 10
res[3] ();/ / 10
res[7] ();/ / 10
res[9] ();/ / 10

// Closure encapsulation can reach the output of res[n]() is n
function createFunctions(){
    var result = new Array(a);for (var i = 0; i < 10; i++) {
        result[i] = function(num){
            return function(){
                return num;
            };
        }(i); // Call the function immediately
    }
    return result;
}
var res = createFunctions();
res[0] ();/ / 0
res[3] ();/ / 3
res[7] ();/ / 7
res[9] ();/ / 9

// Use let's block-level scope
function createFunctions(){
    var result = new Array(a);for (let i = 0; i < 10; i++) {
        result[i] = function(){
            return i;
        };
    }
    return result;
}
var res = createFunctions();
res[0] ();/ / 0
res[3] ();/ / 3
res[7] ();/ / 7
res[9] ();/ / 9
Copy the code
// The closure is formed. Total is referenced by the outer layer without being destroyed.
var result = [];
var a = 3;
var total = 0;

function foo(a) {
    for (var i = 0; i < 3; i++) {
        result[i] = function () {
            total += i * a;
            console.log(total);
        }
    }
}

foo(1);
result[0] ();/ / 3
result[1] ();/ / 6
result[2] ();/ / 9
Copy the code
/ / throttling throttle
const div1 = document.getElementById("div1");
function throttle(fn, delay = 100) {
  let timer = null;

  return function () {
    if (timer) {
      return;
    }
    timer = setTimeout(() = > {
      fn.apply(this.arguments);
      timer = null;
    }, delay);
  };
}

div1.addEventListener(
  "drag",
  throttle(function (e) {
    console.log(e.offsetX, e.offsetY); }));Copy the code
/ / stabilization debounce
const input1 = document.getElementById("input1");
function debounce(fn, delay = 500) {
  // Timer is in the closure
  let timer = null;

  return function () {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() = > {
      fn.apply(this.arguments);
      timer = null;
    }, delay);
  };
}

input1.addEventListener(
  "keyup",
  debounce(function (e) {
    console.log(e.target);
    console.log(input1.value);
  }, 600));Copy the code
// The function is currified
//add(1)(2)(3) = 6;
//add(1, 2, 3)(4) = 10;
//add(1)(2)(3)(4)(5) = 15;
function add(. args) {
  let allArg = args;
  var _adder = function () { allArg.push(... arguments);return _adder;
  };
  _adder.toString = function () {
    return allArg.reduce(function (a, b) {
      return a + b;
    }, 0);
  };
  return _adder;
}
console.log(add(1) (2) (3).toString()); / / 6
console.log(add(1.2.3) (4).toString()); / / 10
console.log(add(1) (2) (3) (4) (5).toString()); / / 15
console.log(add(2.6) (1).toString()); / / 9
Copy the code

conclusion

Execution context at the end of A function call will be destroyed, so A function will also destroy its own internal variables when the context destroyed, but the closure is different, if A function calls the end of the return is A function, will also package return to go out together, the scope of the current context so although A function of the context is destroyed, However, variables in function A can still be found in memory by identifying the index in the scope chain. Key point: A closure is a knapsack, in context execution, but not affected by context destruction.

In addition, in order to be easy to understand, many descriptions in this paper will be more colloquial, sometimes missing part of the accuracy. If you want to learn more systematically, you are recommended to check out JavaScript Advanced Programming, which is partially referenced in this article. “JavaScript advanced Programming” every read benefit, highly recommended. Thank you for your comments.