Home  >  Article  >  Web Front-end  >  An experienced driver will help you thoroughly understand the various pitfalls of JS closures

An experienced driver will help you thoroughly understand the various pitfalls of JS closures

angryTom
angryTomforward
2019-11-25 17:00:433255browse

An experienced driver will help you thoroughly understand the various pitfalls of JS closures

The old driver will help you thoroughly understand the various pitfalls of JS closures

Closures are js Common development techniques, what are closures?

A closure refers to a function that can access variables in the scope of another function. To put it clearly: a closure is a function that can access variables in the scope of other functions. eg:

function outer() {
     var  a = '变量1'
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}

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, so inner is a closure function. Although the definition is very simple, there are many pitfalls, such as this pointer 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 closure functions access the scope of other functions?

Looking at js functions from the perspective of the stack

Basic variables The value of is generally stored in the stack memory, while the value of the object type variable 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:

An experienced driver will help you thoroughly understand the various pitfalls of JS closures

When we execute b={m:30}, there is a new object {m:30} in the heap memory , b in the stack memory points to the new space address (pointing to {m: 30}), and the original {m: 20} in the heap memory will be garbage collected by the program engine, saving memory space. We know that js functions are also objects, and they are also stored in heap and stack memory. Let’s take a look at the transformation:

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

An experienced driver will help you thoroughly understand the various pitfalls of JS closures

**

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 an execution environment of fn. This environment contains variable b and function object fn1. Here you can access the variables defined by its own execution environment and the global execution environment

3 Enter fn1. At this time, the stack memory will push an execution environment of fn1. There are no other variables defined in it, but we can access the variables in fn and the global execution environment, because when the program accesses the variables, it moves to the bottom stack. If you find that there is no corresponding variable in the global execution environment, the program will throw an underfined error.

4 As fn1() is executed, the execution environment of fn1 is destroyed by cup, and then fn() is executed, the execution environment of fn will also be destroyed, leaving only the global execution environment, and now there is no b Variables, and fn1 function objects, only a and fn (the function declaration scope is under window)

**

Accessing a variable within a function is judged according to the function scope chain 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 variable b in fn1, and find the corresponding fn execution environment according to the scope chain of fn1 variable b. So when the program calls a function, it does the following work: prepare the execution environment, initial function scope chain and arguments parameter object

We now look back at 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 executing var inner = outer(), in fact, the execution environment of outer is not destroyed, because the variable a inside it is still referenced by the function scope chain of inner. When the program finishes executing inner(), this Only when the execution environment of inner and outer will be destroyed and adjusted; the book "JavaScript Advanced Programming" recommends: Because closures will carry the scope of the function containing them, because they will occupy more content than other functions, excessive use of closures , will cause excessive memory usage.

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

Pitfall 1: The referenced variables 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 in the result prints a corresponding number, 1, 2, 3, 4,...,10. This is not actually the case because each closure function accesses the variable i in the outer execution environment. Variable i, as the loop ends, i has become 10, so each closure function is executed, and the result prints 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);    // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样
             }
        }(i)
     }
     return result
}

Pit point 2: this points to the problem

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

Pit point 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,当执行完showId后,el无法释放
    }
    el = null    // 主动释放el
}

Tip 1: Use closures to solve recursive calling problems

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**

es6 is not out Previously, there was a variable promotion problem when using var to define 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, most of them are now defined using es6's let and const.

This article has ended here. For more exciting content, you can pay attention to the JavaScript Video Tutorial column on the PHP Chinese website!

The above is the detailed content of An experienced driver will help you thoroughly understand the various pitfalls of JS closures. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:jianshu.com. If there is any infringement, please contact admin@php.cn delete