JS中的闭包

转自:https://mp.weixin.qq.com/s/puZeIzQ6XCVNIFjrqSz7Tg
收藏:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

参考这位园友的文章:https://www.cnblogs.com/itjeff/p/10106855.html

<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JS中的闭包应用</title>
    <label>JS:监听文本框内容变化</label>
    <input id="input" type="text">

    <!--js中的闭包学习地址: https://mp.weixin.qq.com/s/puZeIzQ6XCVNIFjrqSz7Tg -->
    <!-- <script>

        /*
        *一.闭包的概念和特性
        */
        function makeFab()
        {
           let last=1,current=1;
           return function inner()
           {
              [current,last] = [current + last,current]
              return last;
           }            
        }

        /*
        *这是一个生成斐波那契数列的例子。makeFab的返回值就是一个闭包,makeFab像一个工厂函数,
        每次调用都会创建一个闭包函数,如例子中的fab。
        fab每次调用不需要传参数,都会返回不同的值,因为在闭包生成的时候,它记住了变量last和current,以至于在后续的调用中能够返回不同的值。
        *能记住函数本身所在作用域的变量,这就是闭包和普通函数的区别
        *
        */
        let fab = makeFab();
        console.log(fab());//1
        console.log(fab());//2
        console.log(fab());//3
        console.log(fab());//5



       /*
        *二.闭包——函数式编程之魂
        */
        function confirm(confirmText,confirmCallback,cancelCallback){
               // 插入提示框DOM,包含提示语句、确认按钮、取消按钮
               // 添加确认按钮点击事件,事件函数中做dom清理工作并调用confirmCallback
               // 添加取消按钮点击事件,事件函数中做dom清理工作并调用cancelCallback
        }

        function removeItem (id) {
        confirm('确认删除吗?', () => {
            // 用户点击确认, 发送远程ajax请求
            api.removeItem(id).then(xxx)
        }, () => {
            // 用户点击取消,
            console.log('取消删除')
        })
        }

        /*
        *这个例子中,confirmCallback正是利用了闭包,创建了一个引用了上下文中id变量的函数,
        这样的例子在回调函数中比比皆是,并且大多数时候引用的变量是很多个。 
        试想,如果语言不支持闭包,那这些变量要怎么办?作为参数全部传递给confirm函数,
        然后在调用confirmCallback/cancelCallback时再作为参数传递给它们?显然,这里闭包提供了极大便利。
        *
        */



        /*
        *三.闭包的一些列子
        */

        //1.防抖,节流函数
        /*
        前端很常见的一个需求是远程搜索,根据用户输入框的内容自动发送ajax请求,然后从后端把搜索结果请求回来。
        为了简化用户的操作,有时候我们并不会专门放置一个按钮来点击触发搜索事件,而是直接监听内容的变化来搜索(比如像vue的官网搜索栏)。
        这时候为了避免请求过于频繁,我们可能就会用到“防抖”的技巧,即当用户停止输入一段时间(比如500ms)后才执行发送请求。
        */
        function debounce(func,time){
            let timer=0;
            return function(...args){
                timer && clearTimeout(timer)
                timer = setTimeout(()=>{
                   timer =0;
                   func.apply(this,args)
                },time)
            }
        }
        input.onkeypress = debounce(function(){
            console.log(input.value);//事件处理逻辑
        },500)

       //debounce函数每次调用时,都会创建一个新的闭包函数,该函数保留了对事件逻辑处理函数func以及防抖时间间隔time以及定时器标志timer的引用。

       /**
       *2.节流函数
       **/
       function throttle(func,time)
       {
           let timer =0;
           return function(...args){
               if(timer) return;
               func.apply(this,args)
               timer = setTimeout(() =>timer =0,time)
           }
       }        
    </script> -->

    <!-- js中闭包  学习地址:https://www.cnblogs.com/itjeff/p/10106855.html -->
    <!-- <script type="text/javascript">
            /*
            闭包的本质:在一个函数内部创建另一个函数
            3个特性:
            (1)函数嵌套函数。
            (2)函数内部可以引用函数外部的参数和变量。
            (3)参数和变量不会被垃圾回收机制回收。
            以下以闭包的两种主要形式来研究:
            */  
            
           //第1种形式:函数作为返回值
           function a()
           {
              var name ='dov';
              return function()
              {
                  return name;
              }
           }
           var b = a();
           console.log(b());// 输出:dov
           /*
           解析: 在上面的代码中,a()中的返回值是一个匿名函数,这个函数在
           a()作用域内部,所以它可以获取a()作用域下变量name的值,将这个值作为返回值赋给
           全局作用域下的变量b,实现了在全局变量下获取到局部变量中的变量的值
           */

           //再来看一个闭包经典的例子
           function fn()
           {
               var num =3;
               return function(){
                   var n=0;
                   console.log(++n);
                   console.log(++num);
               }
           }
           var fn1 = fn();
           fn1();//   1  4
           fn1();//   1  5

           /*
           解析:一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,
           但在这个例子中,匿名函数作为fn的返回值被赋值给了fn1,这时候就相当于fn1
           =function(){var n=0...},并且匿名函数内部引用着fn里的变量num,所以变量num
           无法被销毁,而变量n是每次被调用时新创建的,所以每次fn1执行完后它就把属于自己的变量
           连同自己一起销毁,于是最后就剩下了孤零零的num,于是这里就产生了内存消耗的问题。           
           */
          for(var i=0;i<5;++i)
          {
             setTimeout(function(){
                console.log(i+ ' ');
             },100);
          }

          /**
          解析:按照预期它依次输出 1 2 3 4 5 而结果它输出了五次5,这是为什么?
          原来由于js是单线程的,所以在执行for循环的时候定时器setTimeout被安排
          到任务队列中排队等待执行,而在等待过程中for循环就已经在执行,等到
          setTimeout可以执行的时候,for循环已经结束,i的值也已经编程5,所以打印
          出来五个5,那么为了实现预期结果应该如下改:(ps:如果把for循环里面的var变成let
          ,也能实现预期结果)
          */
          for(var i=0;i<5;++i)
          {
             (function(i){
                 setTimeout(function(){
                    console.log(i+' ');
                 },100);
             }(i));
          }
         /*
         引入闭包来保存变量i,将setTimeout放入立即执行函数中,将for循环中的循环值i作为参数
         传递,100ms后同时打印出  1 2 3 4 5
         那如果想实现每隔100ms分别输出数字,该如下改:
         **/
         for(var i=1;i<5;i++)
         {
            (function(i){
                setTimeout(function(){
                   console.log(i);
                },i*100);
            })(i)
         }       
        /*
          在这段代码中,相当于同时启动3个定时器,i*100是为4个定时器分别设置了不同的时间,
          同时启动,但是执行时间不同,每个定时器间隔都是100毫秒,
          实现了每隔100毫秒就执行一次打印的效果。
        **/

        //第2种形式:闭包作为参数传递
        var num0 =15;
        var fn1 =function(x){
            if(x>num0)
            {
               console.log(x);
            }
        }

        void function(fn2){
          var num0 =100;
          fn2(30)
        }(fn1)

        /*
        在这段代码中,函数fn1作为参数传入立即执行函数中,在执行到fn2(30)的时候,
        30作为参数传入fn1中,这时候if(x>num)中的num取的并不是立即执行函数中的num,
        而是创建函数的作用域中的num,这里函数创建的作用域是全局作用域下,所以num取的
        是全局作用域中的值 15,即30>15,打印30
        */


        /*
        总结:闭包的好处与坏处:
        好处:
          (1)保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突;
          (2)在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
          (3)匿名自执行函数可以减少内存消耗;

        坏处:
        (1)其中一点上面已经体现,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,
        解决的方法是可以在使用完变量后手动将它赋值为null;
        (2)其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量
        存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。
        **/
    </script> -->


    <!-- js中闭包:阮一峰总结  学习地址:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html -->
    
    



    <!-- js中匿名函数  学习地址:https://www.cnblogs.com/ranyonsue/p/10181035.html -->
    <script>
      //声明普通函数
      function fn()
      {
          console.log("张培月");
      }
      
      //然后将函数的名字去掉即是匿名函数:
      /**匿名函数,咦,运行时,你会发现报错啦!
        function (){
            console.log("张培跃");
        }

        到此,你会发现单独运行一个匿名函数,
        由于不符合语法要求,报错啦!解决方法只需要给匿名函数包裹一个括号即可:
      */
      //匿名函数在其它应用场景括号可以省略

    (function (){
    //由于没有执行该匿名函数,所以不会执行匿名函数体内的语句。
    console.log("张培跃");
    })

    //如果需要执行匿名函数,在匿名函数后面加上一个括号即可立即执行!
    (function (){
        //此时会输出张培跃
        console.log("张培跃");
    })()
    
    //倘若需要传值,直接将参数写到括号内即可:
    (function (str){
    //此时会输出张培跃好帅!
    console.log("张培跃"+str);
    })("好帅!")
    
    /*
    匿名函数的应用场景:  
    1.事件
    */    
    var sub=document.querySelector("#sub");//获得按钮元素
    //给按钮增加点击事件。
    sub.onclick=function(){
        alert("当点击按钮时会执行到我哦!");
    }
    /*
    2.对象
    */
    var obj ={
        name:"张培跃",
        age:18,
        fn:function(){
          return "我叫" + this.name +"今年" + this.age +"岁了!";
        }
    };
    console.log(obj.fn());//我叫张培跃今年18岁了!

    /*
    *3.函数表达式
    */
    //将匿名函数赋值给变量fn
    var fn = function()
    {
        return "我是一只小小鸟,怎么飞也飞不高";
    }
    console.log(fn());//我是一只小小小小留下,怎么飞也飞不高!

    /*
    * 4.回调函数
    */
    setInterval(function(){
      console.log("我其实是一个回调函数,每次1秒钟会被执行一次");
    },1000);

    /*
    *5.返回值
    */
    //将匿名函数作为返回值
    function fn()
    {
        //返回匿名函数
        return function(){
            return "张培跃";
        }
    }

    //调用匿名函数
    console.log(fn()());//张培跃
    //或者如下调用
    var box = fn();
    console.log(box());//张培跃

    /*
    模仿块级作用域
    块级作用域,有的地方称为私有作用域。JavaScript中是没有块级作用域的,例如:
    */
    if(1==1) //条件成立,执行if代码块语句
    {
      var a =12;//a 为全局变量
    }
    console.log(a);//12

    for(var i=0;i<3;i++)
    {
       console.log(i);
    }
    console.log(i);//3

    /*
    * if(){} for(){} 等没有自己的作用域,
    如果有,出了自己的作用域,声明的变量就会立即被销毁了。
    但是咱们可以通过匿名函数来模拟块级作用域:
    */    
    
    (function(){
     //这里是我们的块级作用域(私有作用域)
    })();


    //尝试块级作用域:
    function fn(){

      (function(){
          var la="啦啦啦!";
      })();

      console.log(la);//报错---la is not defined
    }
    fn();

    /*
    *总结:匿名函数的作用:
    1.通过匿名函数可以实现闭包,闭包是可以访问在函数作用域内定义的变量的函数,
    若要创建一个闭包,往往需要用到匿名函数
    2.模拟块级作用域,减少全局变量。执行完匿名函数,存储在内存中相对应的变量会被销毁,
    从而节省内存。再者,在大型多人开发的项目中,使用块级作用域,
    会大大降低命名冲突的问题,从而避免产生灾难性的后果。
    自此开发者再也不必担心搞乱全局作用域了          
    */
    </script>
</head>
<body>
    <div>
        <label>JS:监听文本框内容变化</label>
        <input id="input">
        <!-- 匿名函数的应用场景:1.事件 -->
        <input type="button" value="点我啊!" id="sub">
    </div>
</body>
</html>
“fool me once,shame on you. fool me twice, shame on me.”,翻译过来的意思是“愚弄我一次,是你坏;愚弄我两次,是我蠢”。
原文地址:https://www.cnblogs.com/newcapecjmc/p/13814179.html