JavaScript闭包-从根本上理解闭包

先来说一下JavaScript中的变量作用域
变量作用域分为:局部变量和全局变量。
Javascript中的局部变量只存在于函数中,在函数外部是无法访问的。但是函数内部是可以访问全局变量的

var a = 10
function test () {
    var b = 20
    console.log(b)   // b=20
    console.log(a)   // a=10
}
console.log(a)       // a=10
console.log(b)       //b is not defined

那么如何能从外部获取函数内的局部变量呢?
那就是在函数内部再定义一个函数

function f1(){
    var a = 10
    function f2(){
        console.log(a)   //a是可以被访问到的
    }
}

上面的代码中,f1函数内部定义了一个f2函数,f2可以访问f1里的所有变量,反之f1不能访问f2中的变量。这里需要提到一个js的一个概念叫做链式作用域,子对象会一级一级的向上寻找所有父对象的变量,所以子对象可以访问父对象的所有变量,反之父对像不能访问子对象的变量。

现在f2可以访问f1里的值了,那么我们把f2函数返回出来不就可以在f1函数外部访问它里面的变量了么

function f1(){
    var a = 10
    return function f2(){
        console.log(a)
    }
}
var result = f1()
result()   // a = 10

至此,我们把f2叫做闭包(闭包本质上将函数内部和函数外部连接起来的一座桥梁)

概念:

在一个函数里面嵌套另一个函数,被嵌套的那个函数的作用域就是一个闭包。
作用:可以操作局部作用域的变量,变量不会被浏览器回收,保存变量的值。

应用

1.操作局部作用域的变量

<script type="text/javascript">
    function outerFun () {
        var num = 2;
        //定义一个内部函数
        function innerFun () {
            //内部函数的返回值是外部函数的一个局部变量
            return num;
        }
        //把局部变量的值++
        num++;
        // 返回内部函数
        return innerFun;
    }
    var num = outerFun()();  // 3
    alert(num);  
</script>

上例中虽然函数的声明在num++之前,但是函数返回的时候num已经++过了,所以最后的结果是num++之后的值。即闭包中使用的局部变量的值,一定是局部变量最后的值。

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是”nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

使用闭包的问题

1、闭包会使函数的变量保存在内存中,内存消耗过大所以不能滥用 会导致网页卡顿等问题。
2、闭包中父函数中变量值的改变,会影响子函数中的引用的变量值。如下面的for循环问题。
for循环的问题

<body>
    <input type="button" value="按钮1"    >
    <input type="button" value="按钮2"    >
    <input type="button" value="按钮3"    >
    <script type="text/javascript">
        var btns = document.getElementsByTagName("input");
        for (var i = 0; i < 3; i++) {
            btns[i].onclick = function () {
                alert("我是第" + (i + 1) + "个按钮");
            };
        }
    </script>
</body> 

上面的代码在点击三个按钮的时候都是弹出‘我是第四个按钮’,这个错误的原因就是闭包导致的,每循环一次就会有一个匿名函数设置点击事件,闭包总是保持的变量的最后一个值,所以点击的时候,总是读的是i的最后一个值4

解决办法1
我们可以给每个按钮添加一个属性,来保存每次i的临时值

<body>
    <input type="button" value="按钮1"    >
    <input type="button" value="按钮2"    >
    <input type="button" value="按钮3"    >
    <script type="text/javascript">
        var btns = document.getElementsByTagName("input");
        for (var i = 0; i < 3; i++) {
            //把i的值绑定到按钮的一个属性上,那么以后i的值就和index的值没有关系了。
            btns[i].index = i;
            btns[i].onclick = function () {
                alert("我是第" + (this.index + 1) + "个按钮");
            };
        }
    </script>
</body>

解决办法2
或者使用匿名函数的自执行

<body>
    <input type="button" value="按钮1"    >
    <input type="button" value="按钮2"    >
    <input type="button" value="按钮3"    >
    <script type="text/javascript">
        var btns = document.getElementsByTagName("input");
        for (var i = 0; i < 3; i++) {   
            //因为匿名函数已经执行了,所以会把 i 的值传入到num中,注意是i的值,所以num
            (function(num){
                btns[i].onclick = function(){
                    alert("我是第" + (num + 1) + "个按钮")
                }
            })(i)
        }
    </script>
</body>

参考链接

http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://segmentfault.com/q/1010000011444538

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值