Understanding of closures in js

1. The definition of
closure A functional closure refers to a function that can access variables in the scope of another function. To be clear: A closure is a function that can access variables in the scope of other functions.
For example

function outer() {
     var  a = 'a1'
     var  inner= function () {
            console.log(a)
     }
     // text 就是一个闭包函数,能够访问closure函数的作用域
    return text 
}

// In fact, the closure is defined from the perspective of scope, because inner accesses variables in the outer scope, so inner is a closure 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 outer scope, inner is a closure function. Although the definition is simple, there are many pitfalls, such as the this point and the scope of variables. A little carelessness may cause memory leaks. Let's put the problem aside and think about a question: Why can a closure function access the scope of other functions?

Looking at js functions from the perspective of the stack    The
  values ​​of basic variables are generally stored in the stack memory, while the value of the variable of the object type is stored in the heap memory, and the stack memory stores the corresponding space address. Basic data types: Number, Boolean, Undefined, String, Null.

var  a = 1   //a是一个基本类型
var  b = {
    
    m: 20 }   //b是一个对象

Corresponding memory storage:

Insert picture description here

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

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

Insert picture description here

The stack is a first-in-last-out data structure:
1. Before executing fn, we are in the global execution environment (the browser is the window scope), and there is a variable a in the global scope;
2. Enter fn, at this time The stack memory will push a fn execution environment, this environment has variable b and function object fn1, here you can access the variables defined by your own execution environment and the global execution environment
3. Enter fn1, then the stack memory will push a fn1 There are no other variables defined in the execution environment, but we can access the variables in fn and the global execution environment, because when the program accesses variables, it looks for the underlying stack one by one. If you find there is no corresponding variable in the global execution environment , The program throws an underfined error.
4. With the execution of fn1() completed, the execution environment of fn1 is destroyed by the cup, and then after the execution of fn(), the execution environment of fn will also be destroyed. Only the global execution environment is left, and there are no b variables and fn1 functions. Object, only a and fn (function declaration scope is under window)

Accessing a variable in a function is based on the function scope chain to determine whether the variable exists, and the function scope chain is initialized by the program according to the execution environment stack where the function is located, so in the above example, we print the variable b in fn1 , According to the scope chain of fn1, find the variable b corresponding to the fn execution environment. So when the program calls a function, it does the following work: prepare the execution environment, the initial function scope chain and the arguments parameter object

We now look back to the original example outer and inner

function outer() {
    
    
     var  a = '变量1'
     var  inner = function () {
    
    
            console.info(a)
     }
    return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
var  inner = outer()   // 获得inner闭包函数
inner()   //"变量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 function scope chain. When the program finishes inner(), at this time, The execution environment of inner and outer will be destroyed and adjusted; the book "JavaScript Advanced Programming" suggests that: because closures will carry the scope of the function that contains it, because they will occupy more content than other functions, excessive use of closures will cause Lead to excessive memory usage.

Now that we understand the closure, the corresponding scope and scope chain, return to the topic:

Pit 1: The referenced variable may change

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

It seems that each closure function of result prints the corresponding number, 1, 2, 3, 4,..., 10, which is actually not, because the variable i accessed by each closure function is the variable i in the outer execution environment, with the loop At the end, i has become 10, so execute each closure function and print 10, 10, …, 10 as a result.
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);   // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样
             }
        })(i)
     }
     return result
}

Pit 2: this points to the problem

var object = {
    
    
     name: "object",
     getName: function() {
    
    
        return function() {
    
    
             console.info(this.name)
        }
    }
}
object.getName()()    // underfined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows

Pit 3: Memory leak problem

function  showId() {
    
    
    var el = document.getElementById("app")
    el.onclick = function(){
    
    
      aler(el.id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    }
}

// 改成下面
function  showId() {
    
    
    var el = document.getElementById("app")
    var id  = el.id
    el.onclick = function(){
    
    
      aler(id) 
    }
    el = null    // 主动释放el
}

Tip 1: Use closures to solve the problem of recursive calls

function  factorial(num) {
    
    
   if(num<= 1) {
    
    
       return 1
   } else {
    
    
      return num * factorial(num-1)
   }
}
var anotherFactorial = factorial
factorial = null
anotherFactorial(4)   // 报错 ,因为最好是return num* arguments.callee(num-1),arguments.callee指向当前执行函数,但是在严格模式下不能使用该属性也会报错,所以借助闭包来实现


// 使用闭包实现递归
function newFactorial = (function f(num){
    
    
    if(num<1) {
    
    return 1}
    else {
    
    
       return num* f(num-1)
    }
}) //这样就没有问题了,实际上起作用的是闭包函数f,而不是外面的函数newFactorial

** Tip 2: Use closures to imitate block-level scope **
Before es6 came out, there was a variable promotion problem with var defined variables, eg:

for(var i=0;i<10; i++){
    
    
    console.info(i)
}
alert(i)  // 变量提升,弹出10

//为了避免i的提升可以这样做
(function () {
    
    
    for(var i=0; i<10;i++){
    
    
         console.info(i)
    }
})()
alert(i)   // underfined   因为i随着函数的退出,执行环境销毁,变量回收

Of course, now most of the let and const definitions of es6 are used.

Guess you like

Origin blog.csdn.net/super__code/article/details/90446509