闭包

2018-11-27  本文已影响0人  huanghaodong

什么是闭包

“高三”上解释——闭包是指有权访问另一个函数作用域中变量的函数。
通俗的将,若一个函数的执行需要用到其他的函数内变量,那么这个函数就闭包。如:

function a(){
        var i = 1;
        function b(){   //b就是一个闭包,只是我们无法在函数a外部调用这个闭包。
            console.log(i);
        }
    }

闭包可以在外部环境中被调用

function a(){
        var i = 1;
        return function b(){
                console.log(i);
            }
    }

var test = a();//函数a返回匿名函数,即闭包,给变量test

test();//执行闭包

闭包的作用

主要作用用两个,一是调用其它函数中的局部变量,二是将其它函数中的局部变量保存在内存中。

<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <script>
        function f1(){
    var n=999;
    nAdd=function(){n+=1}//nAdd是一个全局变量,实现在任何时候任何地方都可以控制f1函数内部的n
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000
//一共调用了两次闭包,第一次弹出999,第二次弹出1000,说明了局部变量n一直保存在内存中
        </script>
    </head>
    <body>
        
    </body>
    </html> 

小心闭包!

1.闭包有一个特性,就是只能保存外部环境变量的最后一个值。我们通过一个典型的例子来说明:
我们尝试通过for循环来为一个空数组arr赋值

    var arr = [];
    for(var i = 0;i < 5;i++){
        arr[i] = function(){
            return i;
        }
    }
    //测试arr数组
    console.log(arr[0]());//5
    console.log(arr[1]());//5
    console.log(arr[2]());//5
    console.log(arr[3]());//5
    console.log(arr[4]());//5
控制台的输出结果让我很是吃惊,尼玛弄撒呢?不是应该是0,1,2,3,4吗?咋全是5!
正如开头所说闭包只能保存外部环境变量的最后一个值,外部环境变量i循环结束过后最终值为5,所以闭包中所有的i都为5。好的,讲完了。。。呵呵,这里肯定还是会有许多顽强的小玩伴会问:为啥?
现在我们来深入理解一下上面这个函数的运行机制。上面函数每一次循环都会将function(){return i;}这个匿名函数直接赋值给每一个数组项。然后通过arr[0]()调用执行匿名函数,此时匿名函数需要访问变量i,所以匿名函数会在外部作用域中寻找i,根据闭包的定义,无疑这个匿名函数永远都是一个闭包,闭包只能保存外部环境变量的最后一个值,此时的i很明显等于5,所以arr[0]()会返回5。另外三个数值项也是这个道理。
问题的原因找到了,那怎么才能得到我们想要的0,1,2,3,4呢?每一个数组项的匿名函数function(){return i;}返回值都是通过在外部作用域中找到的,但是等匿名函数开始寻找时,外部的i早已成了一个定值5。我是否可以在外部作用域和匿名函数之间再添加一个作用域(函数),匿名函数从这个新增作用域中获取所需变量。新增函数通过立即执行的形式,从外部环境实时获取参数,相当于在新增函数作用域内定义了一个变量,这个变量就是我们的匿名函数执行时所需要的变量!
    var arr = [];
    for(var i = 0;i < 5;i++){
        (function(m){
            arr[m] = function(){
                return m;
            }
        })(i);
    }
    //测试arr数组
    console.log(arr[0]());//0
    console.log(arr[1]());//1
    console.log(arr[2]());//2
    console.log(arr[3]());//3
    console.log(arr[4]());//4
拿arr[0]来说,arr[0]的值为function(){return m;},现在执行它,arr[0]()需要在外部作用域中寻找变量m,果然在它的包含函数中找到了m,因为这个包含函数是一个立即执行的函数表达式(IIFA),它在for循环时就已经实时地传入了实参i并赋值给形参m,所以arr[0]()就会返回我们想要的结果。这里的IIFA有两个效果:1、封装一个函数作用域。2、立即执行函数以传递实参(如果函数不执行时不会传递参数的)。
IIFE也可以这样写:
for(var i = 0;i < 5;i++){
  arr[i] = (function(m){    //arr[i]中的i具有实时性,所以arr[i]在IIFA内里面外面都行。问题的关键是我们必须将闭包包含在IIFA中,然后闭包向IIFA索取变量。
    return function(){
      return m;
    }
  })(i);        
}

疑惑差不多就解决了吧。还有一个开发中常碰到的类似问题,批量注册事件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
    li{
        list-style: none;
        float: left;
        width:200px;
        height:200px;
        border:1px solid red;
    }
    </style>
</head>
<body>
    <ul>
        <li class="li1"></li>
        <li class="li2"></li>
        <li class="li3"></li>
        <li class="li4"></li>
        <li class="li5"></li>
    </ul>
    <script type="text/javascript">
        //获取元素
        var lis = document.getElementsByTagName("li");
        //批量注册事件
        for(var i = 0;i<lis.length;i++){
            //iife 解决批量添加监听的时候出现的问题 尽量避免在事件函数中调用外部的变量,特别是循环变量,因为闭包。
            //解决办法一
            //写法一
            /*(function(m){lis[m].onclick = function(){
              console.log("我是老"+(m+1));
              }
            })(i)*/
            
            //写法二
            lis[i].onclick = (function(m){
              return function(){
                console.log("我是老"+(m+1));
              }
            })(i)

            //解决办法二
            //给每个li强行添加属性 让这个li记住i  
            // lis[i].o=i;
            // lis[i].onclick = function(){
            //      //输出this 
            // alert("我是老"+(this.o+1))
            // }
        }
    </script>
</body>
</html>

2.因为闭包可以保存其它函数的局部变量,所以闭包比普通函数更占用内存。解决办法,在闭包调用结束后将闭包调用函数赋值为null。

上一篇下一篇

猜你喜欢

热点阅读