JS课程学习之闭包

107 阅读4分钟

我们本章学习搞懂三个概念:
1.什么是闭包?
2.闭包是什么时候创建的?
3.对闭包的理解?

什么是闭包

首先来看维基百科上闭包的定义:
闭包是引用了自由变量的函数,这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
闭包也可看成是由函数和与其相关的引用环境组合而成的实体。

有没有觉得过于抽象,别急,我们用代码说话:

function f1(){
    var m=10;
    var b=20;
    function f2(){
        console.log(m);
    }
    f2();
}
f1();//10

对上述代码来说,f2引用了变量m,那么a就和f2函数一同存在,f2即为该段代码中的闭包。

可概括为:一个函数在另一个函数中定义,内部的函数就称之为闭包函数,它可以访问到父函数的成员。
【查看闭包的方式:先按f12,然后给console.log那一行设置一个断点,既可以看到Closure中的内容】

还记得上面说的:“即使离开了创造它的环境也不例外”吗?

function f1(){
    var a=10;
    var b=20;
    return function f2(){
        console.log(a);
    }
}
var result = f1();  //此时变量result接收的就是function f2
result(); //输出结果为10

创造变量a的环境是f1,重新调用一个就是重新开了一个词法环境;第二个result相当于已经离开了创造它的环境。
即使f2已经离开了f1的环境,但是依然能访问到a。

闭包是什么时候创建的

仍以上述代码为例:调用f1的时候,会调用f1的词法环境;把a和b加进去;运行到return f2的时候已经创建了闭包。

闭包的理解

  • 一个函数没用到父函数的东西,会产生闭包吗?
function f1(){
    var m=10;
    function f2(){
        console.log("aaaa");
    }
    f2();
}
f1();

答案是:不会。
此段代码的f2并没用到父函数f1中的变量m,通过f12查看发现并没产生Closure。

  • 若没用到父级中的东西,而是用到了父级中的父级的东西,即下段代码中的f3用的是f1的变量,此刻会产生闭包吗?
function f1(){
    var m=10;
    function f2(){
        var n=20;
        function f3() {
            console.log(m);
        }
        f3();
    }
    f2();
}


f1();

答案是:会。

闭包的好处

  • 减少全局变量
function add() {
    var a=0;
    a++;
    alert(a);
}
add();
add();

此时的结果是:弹出两次1. 实际想要的是调用一次加1的效果,弹出1,2.

先使用全局变量进行改进:

 var a=0;
function add() {
    a++;
    alert(a);
}
add();
add();

此时能达到效果。

再利用闭包进行改进:

function f() {
    var a=0;
    return function() {
        a++;
        alert(a);
    }
}
var result = f();
result();
result();

此时也能达到效果,且减少了全局变量的污染。

  • 减少传递给函数的参数变量
function calFactory(base){
    return function(max) {
        var total = 0;
        for(var i=1;i<=max;i++){
            total += i;
        }
        return total + base;
    }
}
var adder = calFactory(2);  //此时捕获的base是2
alert(adder(3));//结果为8 即2+(1、2、3)

var adder2 = calFactory(1); //此时捕获的base是1
alert(adder2(3));//结果为7,即1+(1,2,3)
  • 封装
(function () {
    var m=0;
    function getM() {
        return m;
    }
    function setM(val) {
        m=val;
    }
    window.g = getM; //此处不是函数调用
    window.f = setM;
})();

f(12);
alert(g());  //此时输出为12

闭包使用的注意点

  • 对捕获的变量只是引用,不是复制
function f() {
    var num =1;
    function g() {
        alert(num);
    }
    num++;
    g();
}
f();//此时输出结果为2

为什么输出结果是2而不是1?
代码执行过程如下:
先创建f的词法环境,f.le,然后到function g的时候闭包已经产生了,g.scope指向f.le,然后num++为2,是在f的词法作用域中的,最后执行g的时候,从f.le中取到num。

  • 父函数每次调用会产生不同的闭包
function f() {
    var num =1;
    return function () {
        num++;
        alert(num);
    }
}
var result = f();
result(); //2
result();//3

var result1 = f();
result1();//2
result1();//3

第一次调用f时,创建词法作用域f.le,此时即使调用了两次f,仍然都是同一个词法作用域,所以会输出2,3; 第二次调用f时,创建了新的词法作用域,此时后面两次result1调用,也都是同一个的新的这个词法作用域。

  • 循环中问题
<div id="1">1</div>
<div id="2">2</div>
<div id="3">3</div>

<script type="text/javascript">
    for(var i=1;i<=3;i++){
        var ele = document.getElementById("i");
        ele.onclick=function(){
            alert(i);
        }
    }
</script>

结果点击id为1,2,3的弹框内容均为4;

将代码修改如下:

for(var i=1;i<=3;i++){
   var ele = document.getElementById(i);
   ele.onclick=(function (id) {
       return function(){
           alert(id);
       }
   })(i);
}

立即执行函数中包裹着一个闭包;立即执行函数传回的参数值为i,通过id进行接收,接收到i,然后再弹出,即对应div1,div2,div3弹出1,2,3,而不是4,4,4,用到了第二条:父级函数每调用一次,就产生一个新的闭包。