js中的闭包

一、 概念

  • 把局部变量闭包成当前作用域的私有变量(把局部变量拿到作用域的外部使用)
  • js的作用域分两种,全局和局部,基于我们所熟悉的作用域链相关知识,我们知道在js作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,反之则不能,也就是说在外层作用域下无法获取内层作用域下的变量,同样在不同的函数作用域中也是不能相互访问彼此变量的,那么我们想在一个函数内部也有限权访问另一个函数内部的变量该怎么办呢?闭包就是用来解决这一需求的,闭包的本质就是在一个函数内部创建另一个函数。

1.为什么要使用内嵌函数

  • 因为变量属于局部变量,如果是全局的话没必要使用闭包。

2.为什么要使用return呢?

  • 因为如果不 return,你就无法使用这个闭包。把 return bar 改成 window.bar = bar 也是一样的,只要让外面可以访问到这个 bar 函数就行了。
  • 所以 return bar 只是为了 bar 能被使用,也跟闭包无关

二、优点

  • 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
  • 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
  • 匿名自执行函数可以减少内存消耗

三、缺点

  • 闭包的变量会存到内存里面 , 大量使用闭包会导致内存泄漏。(被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null)
  • 由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

四、闭包的作用

  • 访问函数内部的局部变量(闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」)

五、闭包三个特性

  • (1)函数嵌套函数。
  • (2)函数内部可以引用函数外部的参数和变量。
  • (3)参数和变量不会被垃圾回收机制回收,但是可以手动回收。

六、闭包有关题目

  function method(){
       var name = "张三";  //闭包变量
       return function(){  //闭包函数
           console.log(name);
       }
   }
   method()();
   //或者
   var fun = method()();
   console.log(fun);

在这里插入图片描述

  • 其中method()();和fun就等价于执行function()函数
  function method(){
       var num = 1;
       return function(){
           num = num+5;
           console.log(num);
       }
   }
   var fun = method();
   fun();//6
   fun();//11
	function person(name){
        //变量作用域为函数内部,外部无法访问,防止了变量名冲突和污染
        var name = '小明';
        this.sayName=function(){
            alert(name)
        }
        this.changeName=function(newName){
            name=newName;
        }
    }
    //外部无法访问内部变量
    let a=new person();
    console.log(a.name);//undefined
    a.changeName('小红');
    //这里修改的name会保存下来
    a.sayName();//小红 
	function method3(){
       var num=1;
       return function(){
           var n=10;
           num++;
           n++;
           console.log(num, n);
       }
       num=null;
   }
   var f=method3();
   f();//2 11
   f();//3 11
   var arr = [];
   for(var i = 0;i<10;i++){
       function list(){
           console.log(i);
       }
       arr.push(list);//往数组里面追加
   }
   console.log(arr);
   arr[0]();
   arr[1]();
   arr[2]();
   arr[3]();

在这里插入图片描述

  • 因为js为单线程,必须等到for循环执行完之后才会输出(变量在执行循环的时候会覆盖,等循环执行完即变量为10是才出代码块,所以输出全为10)。
  • 那么怎样改成arr[0] = 0,arr[1] = 1…arr[9] = 9这种形式呢?
  • 只要把局部变量i放到外部使用即可-----把list()函数改成闭包函数
    var arr = [];
    for(var i = 0;i<10;i++){
       var f = (function(n){
           return function(){
               console.log(n);
           }
       })(i);
       arr.push(f);
    }
    console.log(arr);
    arr[0]();
    arr[1]();
    arr[2]();
    arr[3]();

在这里插入图片描述

   function aaa(){
       var name = "xxx";
       return function bbb(){
           alert(name);
       }
   }
   aaa()();

在这里插入图片描述

  • 线程相关的闭包
  • 一次性计时器,延迟1000ms输出
  • setTimeout()—延迟多少毫秒去执行的一个回调函数。
   setTimeout(function(){
       console.log("执行代码");
   },1000);

在这里插入图片描述

   for(var i = 0;i<10;i++)
   {
       setTimeout(function(){
           console.log(i);
       },0);
   }

在这里插入图片描述

  • 为什么会输出10个10?(跟js是单线程以及异步操作有关)
  • js属于单线程,计时器要等队列,得一个一个执行,for循环很快建立了10个,计时器排成一个队列,它再去执行计时器得等排完之后才会去执行,因为js是单线程的,它排队列的时候,for循环早就完成了。
  • 所谓单线程就是在某个特定的时刻只有特定的代码能够被执行,并阻塞其他的代码,简单来说就是一次只能执行一个命令,不能同时进行多个命令。
  • 为什么js是单线程呢?
  • 因为js主要运行在浏览器中,比如有两个命令:命令一是删除DOM1,命令二是修改DOM1属性,如果js是多线程,两个命令同时执行就会造成错误,但是单线程的话,就会为DOM1上锁,每次只有一个命令有效
  • 那么,既然js是单线程,异步操作是怎么实现的呢?
  • js在执行时会生成一个主任务队列(先进先出),队列里的任务会按照顺序一个一个的运行,当使用setTimeout时,setTimeout会将里面的函数放到异步队列中,当前面的任务都执行完成后,js通过eventloop事件循环,发现异步队列中有任务在等待,于是将其添加到主队列中开始执行。
  • 同步代码是按照先后顺序依次执行。
  • 异步代码是常规代码执行完毕,再找异步队列执行。
  • 那么如何让他输出0-9呢?
  • 闭包,把局部变量拿到外部使用。
   for(var i = 0;i<10;i++)
   {
       (function(n){
           setTimeout(function(){
               console.log(n);
           },0);
       })(i)
   }

在这里插入图片描述

   function my()
   {
       var n = 999;
       return function(){
           n++;
           console.log(n);
       }
   }
   my()();
   my()();
   var f = my();
   f();
   f();
   f();

在这里插入图片描述

  • 事件闭包
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<script>
   //事件闭包
   addHandle();
   function addHandle(){
       var btn=document.getElementsByClassName("btn");//闭包变量
       for(var i=0;i<btn.length;i++){
           (function (n){
               btn[n].onclick=function(){
                   console.log("点我", n);
               }
           })(i);
       }
       //手动回收
       btn=null;
   }
</script>
	function fun(n,o){
        console.log(o);
        return{
            fun: function (m) {
                return fun(m,n)
            }
        };
    }
    var a=fun(0);a.fun(1);a.fun(2);a.fun(3);//undefined 0 0 0
    var b=fun(0).fun(1).fun(2).fun(3);//undefined 0 1 2
    var c=fun(0).fun(1);c.fun(2);c.fun(3);//undefined 0 1 1
  • 第一行a
  • var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
  • 可以得知,第一个fun(0)是在调用第一层fun函数。第二个fun(1)是在调用前一个fun的返回值的fun函数,所以:
  • 第后面几个fun(1),fun(2),fun(3),函数都是在调用第二层fun函数。
  • 遂:
  • 在第一次调用fun(0)时,o为undefined;
  • 第二次调用fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
  • 第三次调用fun(2)时m为2,但依然是调用a.fun,所以还是闭包了第一次调用时的n,所以内部调用第一层的fun(2,0);所以o为0
  • 第四次同理;
  • 即:最终答案为undefined,0,0,0
  • 第二行b
  • var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
  • 先从fun(0)开始看,肯定是调用的第一层fun函数;而他的返回值是一个对象,所以第二个fun(1)调用的是第二层fun函数,后面几个也是调用的第二层fun函数。
  • 遂:
  • 在第一次调用第一层fun(0)时,o为undefined;
  • 第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
  • 第三次调用 .fun(2)时m为2,此时当前的fun函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次执行第一层fun函数时时(1,0)所以n=1,o=0,返回时闭包了第二次的n,遂在第三次调用第三层fun函数时m=2,n=1,即调用第一层fun函数fun(2,1),所以o为1;
  • 第四次调用 .fun(3)时m为3,闭包了第三次调用的n,同理,最终调用第一层fun函数为fun(3,2);所以o为2;
  • 即最终答案:undefined,0,1,2
  • 第三行c
  • var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
  • 根据前面两个例子,可以得知:
  • fun(0)为执行第一层fun函数,.fun(1)执行的是fun(0)返回的第二层fun函数,这里语句结束,遂c存放的是fun(1)的返回值,而不是fun(0)的返回值,所以c中闭包的也是fun(1)第二次执行的n的值。c.fun(2)执行的是fun(1)返回的第二层fun函数,c.fun(3)执行的也是fun(1)返回的第二层fun函数。
  • 遂:
  • 在第一次调用第一层fun(0)时,o为undefined;
  • 第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
  • 第三次调用 .fun(2)时m为2,此时fun闭包的是第二次调用的n=1,即m=2,n=1,并在内部调用第一层fun函数fun(2,1);所以o为1;
  • 第四次.fun(3)时同理,但依然是调用的第二次的返回值,遂最终调用第一层fun函数fun(3,1),所以o还为1
  • 即最终答案:undefined,0,1,1

七、手动回收闭包的参数或者变量

  • 闭包的参数和变量不会被垃圾回收机制回收,但是可以手动回收。
  • null表示只留有空间,不占位置,所以可以进行手动回收。
	function method(){
       var num=1;
       return function(){
           num++;
           console.log(num);
       }
       //手动回收
       //null表示引用了一处内存,但是不占位置
       num=null;
   }
   var a=method();
   a();//2
   a();//3
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南初️

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值