闭包

作者: huanghaodong | 来源:发表于2018-11-27 14:33 被阅读0次

什么是闭包

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

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。

相关文章

  • swift-闭包

    闭包 闭包定义 闭包简化 - 尾随闭包 闭包参数 闭包返回值 闭包的循环引用

  • 闭包,闭包,闭包

    1、这家伙到底是什么? 网上关于这个的讨论的太多了太多了,有各种的举例子,但是大部分还在寻找这个答案的小伙伴对于变...

  • 闭包-Closures [swift 5.1]

    闭包的语法 尾随闭包 闭包逃离 自动闭包

  • Day7 闭包(Closures)

    本页包含内容:• 闭包表达式• 尾随闭包• 值捕获• 闭包是引用类型• 逃逸闭包• 自动闭包 1、闭包表达式 闭包...

  • Python闭包

    闭包 = 环境变量 + 函数 调用闭包内部的环境变量 闭包的经典误区 闭包与非闭包实现人类走路 非闭包 闭包

  • 闭包(closure)

    ● 闭包基础 ● 闭包作用 ● 闭包经典例子 ● 闭包应用 ● 闭包缺点 ● 参考资料 1、闭包基础 作用域和作...

  • swift- 闭包一

    /*• 闭包表达式• 尾随闭包• 值捕获• 闭包是引用类型• 逃逸闭包• 自动闭包*/

  • (9) python之闭包

    闭包闭包 = 函数 + 环境变量(函数定义的时候) 一个最简单的闭包 闭包不受外部变量影响 非闭包 闭包 闭包 只...

  • Swift-进阶 :闭包(二)逃逸闭包 & 非逃逸闭包

    本文主要分析逃逸闭包 、非逃逸闭包、自动闭包 逃逸闭包 & 非逃逸闭包 逃逸闭包定义 当闭包作为一个实际参数传递给...

  • javascript闭包详解

    跟我念 bi 闭 bao包 ,闭包的闭,闭包的包。。 闭包的简介 在计算机科学中,闭包(英语:Closure),又...

网友评论

      本文标题:闭包

      本文链接:https://www.haomeiwen.com/subject/sdugqqtx.html