JS closure pits

Keywords: Windows Attribute

Links to the original text: https://www.jianshu.com/p/26c81fde22fb

Closure is a common technique in js development. What is closure? Closure refers to a function that can access variables in the scope of another function. Clearly, a closure is a function that can access variables in the scope of other functions. eg:

  function outer() {
         var  a = 'Variable 1'
         var  inner = function () {
                console.info(a)
         }
        return inner    // inner is a closure function because it can access the scope of the outer function.
    }

Many people will not understand the relationship between anonymous functions and closures. In fact, closures are defined from the perspective of scope, because inner accesses variables in the outer scope, so inner is a closure function. Although the definition is simple, there are many pits, such as this pointing, the scope of variables, which may cause memory leaks if you pay little attention to them. Let's put the problem aside and think about one question: Why can closure functions access the scope of other functions?

Viewing js Functions from the Stack Perspective
The values of basic variables generally exist in stack memory, while the values of object-type variables are stored in stack memory, and stack memory stores the corresponding spatial address. Basic data types: Number, Boolean, Undefined, String, Null.

var  a = 1   //A is a basic type
var  b = {m: 20 }   //b is an object

Corresponding memory storage:



When we execute b={m:30}, the heap memory has a new object {m:30}, the stack memory B points to a new spatial address (pointing to {m:30}), and the original {m:20} in the heap memory will be recycled by the program engine garbage, saving memory space. We know that the js function is also an object. It is also stored in stack and stack memory. Let's look at the transformation:

var a = 1;
function fn(){
    var b = 2;
    function fn1(){
        console.log(b);
    }
    fn1();
}
fn();

**
Stack is a data structure of first-in-last-out.
Before fn is executed, we have a variable a in the global execution environment (browser is window scope).
2 Enter fn, then stack memory push es an FN execution environment, which has variables b and function object fn1, where you can access variables defined by your own execution environment and global execution environment.
3 Enter fn1, then stack memory will push an FN1 execution environment. There are no other variables defined, but we can access the variables in FN and global execution environment, because when accessing variables, the program will look to the underlying stack one by one. If no corresponding variables are found in the global execution environment, the process will The order throws an underfined error.

4 With the completion of fn1() execution, the execution environment of fn1 is destroyed by cup, and then the execution environment of fn() will be destroyed. Only the global execution environment is left. There are no b variables and fn1 function objects, only a and fn (function declaration scope is under window s).
**

Accessing a variable in a function is based on the function scope chain to determine whether the variable exists or not, and the function scope chain is initialized by the program according to the execution environment stack where the function resides. So in the above example, we print variable b in FN1 and find the variable b in the corresponding FN execution environment according to the scope chain of fn1. . So when a program calls a function, it does the following things: preparing the execution environment, the scope chain of the initial function, and the arguments parameter object.

Now let's go back to the original examples of outer and inner

function outer() {
     var  a = 'Variable 1'
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner is a closure function because it can access the scope of the outer function.
}
var  inner = outer()   // Obtaining inner closure function
inner()   //"Variable 1"

When the program finishes var inner = outer(), the execution environment of outer is not destroyed, because the variable a in it is still referenced by inner's function scope chain, and when the program finishes inner(), the execution environment of inner and outer will be destroyed. Closures carry the scope of functions that contain them, because they take up more content than other functions, and overuse of closures can lead to overuse of memory.

Now we understand closures, which correspond to scopes and scopes chains, and return to the theme:
Point 1: Variables referenced may change

function outer() {
      var result = [];
      for (var i = 0; i<10; i++){
        result.[i] = function () {
            console.info(i)
        }
     }
     return result
}

It looks as if each closure function of result prints the corresponding number, 1,2,3,4,...,10. Actually, it is not because each closure function accesses variable i in the outer execution environment. With the end of the loop, i has become 10, so each closure function is executed, and the result is printed 10,10,...,10.
How to solve this problem?

function outer() {
      var result = [];
      for (var i = 0; i<10; i++){
        result.[i] = function (num) {
             return function() {
                   console.info(num);    // The num accessed at this time is the num of the upper function execution environment. There are 10 function objects in the array. Numbers in the execution environment of each object are different.
             }
        }(i)
     }
     return result
}

Point 2: this Pointing Problem

var object = {
     name: ''object",
     getName:  function() {
        return function() {
             console.info(this.name)
        }
    }
}
object.getName()()    // underfined
// Because the closure function inside is executed in windows scope, that is, this points to window s.

Point 3: Memory leak problem

function  showId() {
    var el = document.getElementById("app")
    el.onclick = function(){
      aler(el.id)   // This causes the closure to refer to the outer el, which cannot be released after showId is executed
    }
}
// Change to the following
function  showId() {
    var el = document.getElementById("app")
    var id  = el.id
    el.onclick = function(){
      aler(id)   // This causes the closure to refer to the outer el, which cannot be released after showId is executed
    }
    el = null    // Active release of el
}

Tip 1: Solving recursive invocation problems with closures

function  factorial(num) {
   if(num<= 1) {
       return 1;
   } else {
      return num * factorial(num-1)
   }
}
var anotherFactorial = factorial
factorial = null
anotherFactorial(4)   // Error reporting, because it is best to return num* arguments.callee (num-1), arguments.callee points to the current execution function, but in strict mode can not use this attribute will also report errors, so with the help of closures to achieve.


// Recursion using closures
function newFactorial = (function f(num){
    if(num<1) {return 1}
    else {
       return num* f(num-1)
    }
}) //So there's no problem. It's actually the closure function f, not the outside function newFactorial.

** Tip 2: Imitating Block-Level Scopes with Closures**
Before es6 came out, there was a problem of variable promotion when var was used to define variables. eg:

for(var i=0; i<10; i++){
    console.info(i)
}
alert(i)  // Variable boost, pop-up 10

//This can be done to avoid the promotion of i.
(function () {
    for(var i=0; i<10; i++){
         console.info(i)
    }
})()
alert(i)   // underfined because i performs environment destruction and variable recovery as the closure function exits

Of course, most of the definitions now use let and const in es6.

Posted by redbrad0 on Fri, 23 Aug 2019 00:44:05 -0700