一文透析闭包

119 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天

1.如何产生闭包?

当函数a的内部函数b引用了函数a的变量(函数)时,就产生了闭包

2.闭包到底是什么?

  • 理解一:闭包是嵌套的内部函数(绝大部分人)
  • 理解二:闭包是包含被引用变量(函数)的对象(极少数人)

闭包存在于嵌套的内部函数中

可以使用Chrome调试查看

scope:可以看执行上下文

breakpoints:设置端点

closure:闭包的意思

call stack: 调用栈

注意: 函数a中不return 函数b在浏览器中不会看到第二个红圈的内容

3.闭包产生的两个条件

  • 1.函数嵌套
  • 2.内部函数引用了外部函数的变量(函数)
 function a(){
        var x=8;
        function b(){  //执行函数定义就会产生闭包(不用执行内部函数b)
            console.log("b: "+x);
            // return x;
        }
        return b;
    }
    a();

4.常见的闭包

  • 将函数作为另一个函数的返回值
 function a(){
        var x=8;
   	var y;
        function b(){
            x++;
            console.log("b: "+x);
        }
        return b;
    }
    var c=a(); //c指向内部函数b
    c();  //输出 9       //执行c等于执行函数b
		c();  //输出 10
//如果没有闭包,执行完函数后,变量就会销毁,因为局部变量会自动释放,但是因为有闭包的存在,
//会保留闭包的变量(比如会保留x却不会保留y)
  • 将函数作为实参传递给另一个函数调用
    function showDelay(msg,time){
        setTimeout(function(){
            console.log(msg);
        }, time);    //这里只有msg是闭包对象,time不是,因为函数没调用
    }
    showDelay("vueKing",2000)

5.闭包的作用

  • 使函数内部的变量在函数执行完后,仍然存活在内存中(延长的局部变量的生命周期)
  • 可以在函数外部操作函数内部的变量(函数)
 function a(){
        var x=8;
        function b(){
            x++;        //可以通过内部函数操作闭包变量x
            console.log(x);
          return x;     //可以通过return查看闭包变量x
        }
        return b;
    }
    var c=a();
    c();

6.闭包的生命周期

  • 产生: 在嵌套内部函数定义执行完成就产生了(不是函数调用时)
  • 死亡: 在嵌套的内部函数称为垃圾对象时
 function a(){
   			//此时闭包已经产生
        var x=8;
        function b(){
            x++;       
            console.log(x);
          return x;    
        }
        return b;
    }
    var c=a();
    c();
		c=null;  //闭包死亡(因为包含闭包的函数对象成为垃圾对象)

下面的情况执行完那行才产生,为什么?

因为var b=function(){} 只会先声明变量b,而不会先解析函数,函数里面的内容没被解析自然不会产生闭包

 function a(){
        var x=8;
        var b=function(){  //这种情况执行完这行才产生
            x++;       
            console.log(x);
          return x;    
        }
        return b;
    }
    var c=a();
    c();
		c=null; 

7.闭包的应用

自定义js模块

  • 具有特定功能的js文件

  • 将所有的数据和功能都封装在一个函数内部(私有的)

  • 只向外暴露一个包含n个方法的对象或函数

  • 模块的使用只需要通过模块暴露的对象去调用方法来实现相应的功能

方法一:

module.js

function myModule(){
    var msg=1;
    function add(){
        msg++;
        console.log(msg);
    }
    function reduce(){
        msg--;
        console.log(msg);
    }
    return {
        add:add,
        reduce:reduce
    }
}

xxx.html

    <script src="module.js"></script>
<script>
    var module=myModule();
    module.add();
		//输出 2
</script>

方法二(自执行):

module.js

(function (){
    var msg=1;
    function add(){
        msg++;
        console.log(msg);
    }
    function reduce(){
        msg--;
        console.log(msg);
    }
    window.myModule={
        add:add,
        reduce:reduce
    }
})()

xxx.html

    <script src="module.js"></script>
<script>
    // var module=myModule();
    // module.add();

    myModule.add();
		//输出 2
</script>

8.内存溢出与内存泄露

  • 内存溢出:
    • 一种程序运行出现的错误
    • 当程序运行需要的内存超过了剩余的内存时,就出抛出内存溢出的错误
  • 内存泄漏:
    • 占用的内存没有及时释放
    • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:

    -   意外的全局变量
    -   没有及时清理的计时器或回调函数
    -   闭包(没有及时清理闭包的变量)
    

意外的全局变量

//1.意外的全局变量
//函数里面没有用var声明
function fn(){
 	a=3;
}

//2.没有及时清理计时器
setInterval(function(){
  console.log("aa");
},1000)

//3.没有及时清理闭包产生的变量,例子看前面代码

9.闭包面试题

    //闭包,究极递归
    function fun(n,o){
        console.log(o);
        return {
            fun:function(m){
                return fun(m,n);
            }
        }
    }
//闭包变量为n

    var a=fun(0);   //undefined
    a.fun(1);       //0
    a.fun(2);       //0
    a.fun(3);       //0
//12行得到闭包变量n=0,后面只是调用fun方法,传入的参数是m,
n也就是闭包变量一直没变

    var b=fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2
//b.fun(0)得到一个对象,此时闭包变量n=0;
当b.fun(0).fun(1)后得到的还是一个对象,此时的闭包变量n=1;
以此类推

    var c=fun(0).fun(1); //undefined 0
    c.fun(2);            //1
    c.fun(3);            //1
//24行fun(0).fun(1),此时闭包变量n=1;

10.经典误区

    <button>1</button>
    <button>2</button>
    <button>3</button>
<script>
    var btns=document.getElementsByTagName("button")
    for(var i=0;i<btns.length;i++){
        btns[i].onclick=function(){
            alert("第"+(i+1)+"个")
        }
    }
</script>

上面无论点击那个button都是第四个.为什么呢?

首先理解回调函数,回调函数可以理解成按我们需要去执行;

点击事件触发函数执行也就是dom回调函数,

所以上面for循环的时候,因为回调函数会进入任务队列,不会进入主任务队列马上执行,点击事件先跳过,往下继续执行,当执行完时,i已经等于3了,i是全局变量,但是前面的对象绑定的时候i是0,1,2.

var btns=document.getElementsByTagName("button")
    for(var i=0;i<btns.length;i++){
        (function(i){
            btns[i].onclick=function(){
            alert("第"+(i+1)+"个")
        }
        })(i)
        
    }
		console.log(i) //3
//使用闭包,给每一个btns对象一个独立的i的作用域,
而全局的i=3,还是先执行了

改变上面这样就能点击哪个button就显示哪个button的数字了