理解Javascript闭包

一、闭包的定义

闭包就是一个函数。什么函数呢?是能访问另一个函数作用域的函数(可以读取其他函数内部变量的函数)。
由于在Javascript语言中,只有函数内部的子函数才能读取其父函数的局部变量,因此可以把闭包理解为**“定义在一个函数内部的函数”**

二、闭包的作用

闭包主要有两个作用:

  1. 可以读取函数内部的变量
  2. 可以让这些变量的值始终保存在内存中

通过如下代码来理解:

 function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

上述代码函数f1内部定义了一个函数f2,并将其作为返回值,则f2就是一个闭包。由于将f1的执行结果赋值给变量result,那么result实际上就是闭包f2函数。它一共执行了两次,第一次值是999,第二次值是1000.这证明了f1中局部变量n的值一直保存在内存中,并没有在f1调用后被自动消除。

为什么会这样呢?因为f2被赋给了一个全局变量,因此f2会始终在内存中,而f2依赖于f1,因此f1也会始终在内存中,不会在调用结束后被垃圾回收机制回收。

上述代码还有一个需要注意的地方在于nAdd=function(){n+=1},首先在nAdd前面没有关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数,而这个匿名函数也是一个闭包,因此可以通过nAdd对f1内的局部变量进行操作。

三、闭包中需要注意的问题

闭包中需要注意的是,返回的函数并没有立即执行,而是需要再次调用才会执行。
上述代码中,返回的函数f2并没有立即执行,而是调用了result()才执行的。
再看一段代码:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

上述代码,每次循环都创建一个函数,总共创建了3个函数,并将这3个函数放入数组arr中返回了。
你可能认为调用f1(),f2(),f3()的结果分别是:1,4,9,但实际结果是:

f1(); // 16
f2(); // 16
f3(); // 16

全部都是16!
我们分析一下上面代码的执行流程:
首先var results = count();之后,函数count已经被调用了,所以依次执行函数内的各段代码:var arr = [];,for (var i=1; i<=3; i++),这个for循环尤其值得注意。因为此时循环体执行了push方法,将一个个函数function () { return i * i;}添加到数组内,但是这个函数并没有被调用,还只是一个变量,所以for循环依次执行,直到i = 4。因为闭包,内部函数function () { return i * i;}引用的i就是外部变量,for循环中的i = 4。所以,之后数组arr内的函数的i都是4。

​ 调用函数count后,变量results已经是数组arr了。数组里面元素依次是function f1() { return i * i;} function f2() { return i * i;} function f3() { return i * i;}。但是三个函数都没有被调用,直到var f1 = results[0];,此时function f1() { return i * i;}开始执行,如上段所写,此时的i = 4,所以,返回值就是16了。后面两个调用也是类似情况。

因此返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?
**解决方法一:**再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {    //立即执行函数
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

上述代码创建了一个立即执行函数,将循环变量当前的值绑定给了立即执行函数,由于立即执行函数的存在数组里的值依次是function f1() { return 1 * 1;} function f2() { return 2 * 2;} function f3() { return 3 * 3;}。从而达到了调用f1,f2,f3分别得到1,4,9的效果。

解决方法2:
将循环体中的var改成let

主要参考:
https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016
https://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

你可能感兴趣的:(前端,javascript,前端,开发语言)