JS闭包

231 阅读3分钟

什么是闭包

一句话概述就是绑定了执行环境的函数。

函数执行后返回结果是一个内部函数,并被外部变量所引用,如果内部函数持有被函数作用域的变量,即形成了闭包。

可以在内部函数访问到外部函数作用域。使用闭包,一可以读取函数中的变量,二可以将函数中的变量存储在内存中,保护变量不被污染。而正因为闭包会把函数中的变量值存储在内存中,会对内存有损耗,所以不能滥用闭包,否则会影响网页性能,造成内存泄漏。当不需要使用闭包时,要及时释放内存,可将内层函数对象的变量赋值为null。

闭包的特性

  • 函数嵌套函数
  • 函数内部可以引用外部的参数和变量
  • 参数和变量不会被垃圾回收机制回收

闭包原理

函数执行分为两个阶段(预编译阶段和执行阶段)

  • 在预编译阶段,如果发生内部函数使用了外部函数的变量,则会在内存中创建一个"闭包"对象并保存对应变量值,如果已存在"闭包",则只需要增加对应属性值即可。
  • 执行完后,函数执行上下文会被销毁,函数对"闭包"对象的引用也会被销毁,但其内部函数还持用"闭包"的引用,所以内部函数可以继续使用"外部函数"中的变量

利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。

优点

  • 可以从内部函数访问外部函数的作用域中的变量,且访问到的变量长期驻扎在内存中,可供之后使用
  • 避免变量污染全局
  • 把变量存到独立的作用域,作为私有成员存在

缺点

  • 对内存消耗有负面影响。因内部函数保存了对外部变量的引用,导致无法被垃圾回收,增大内存使用量,所以使用不当会导致内存泄漏
  • 对处理速度具有负面影响。闭包的层级决定了引用的外部变量在查找时经过的作用域链长度
  • 可能获取到以外的值

全局变量的累加

<script>
var a = 1;
function msg(){
    a++;
    alert(a);
}
msg();              //2
msg();            //3
</script>

局部变量

<script>

function msg(){
    var a = 1;
    a++;
    alert(a);
}
msg();                    //2
msg();                    //2
</script>

局部变量的累加

<script>
function outer(){
    var x=10;
    return function(){             //函数嵌套函数
            x++;
            alert(x);
    }
}
var y = outer();              //外部函数赋给变量y;
y();                 //y函数调用一次,结果为11,相当于outer()();
y();                //y函数调用第二次,结果为12,实现了累加
</script>

模块化代码,减少全局变量的污染

<script>
var fn = (function(){      //fn为外部匿名函数的返回值
    var a = 1;
    return function(){
            a++;
            alert(a);
    }
})();
fn();    //2 ;调用一次fn函数,其实是调用里面内部函数的返回值    
fn();    //3
</script>

私有成员的存在

<script>
var aaa = (function(){
    var a = 1;
    function bbb(){
            a++;
            alert(a);
    }
    function ccc(){
            a++;
            alert(a);
    }
    return {
            b:bbb,             //json结构
            c:ccc
    }
})();
aaa.b();     //2
aaa.c()      //3
</script>

使用匿名函数实现累加

//使用匿名函数实现局部变量驻留内存中,从而实现累加

<script type="text/javascript">
 
function box(){
     var age = 100;
     return function(){          //匿名函数
          age++;
          return age;
     };
     
 } 
var b = box();
alert(b());
alert(b());    //即alert(box()());
alert(b());
alert(b);            //     function () {
                        //   age++;
                       // return age;
                      //       }

b = null;  //解除引用,等待垃圾回收
</script>

在循环中直接找到对应元素的索引

 <ul>
    <li>123</li>
    <li>456</li>
    <li>789</li>
    <li>010</li>
</ul>
<script>
    window.onload = function(){
            var aLi = document.getElementsByTagName('li');
            for (var i=0;i<aLi.length;i++){
                  aLi[i].onclick = function(){       //当点击时for循环已经结束
                    alert(i);
                  };
            }
    }
</script>

使用闭包改写上面代码:

<script>
    window.onload = function(){
        var aLi = document.getElementsByTagName('li');
        for (var i=0;i<aLi.length;i++){
            (function(i){
                    aLi[i].onclick = function(){
                            alert(i);
                    };
            })(i);
        }
    };
</script>

内存泄露问题

由于IE的js对象和DOM对象使用不同的垃圾收集方法,因此闭包在IE中会导致内存泄露问题,也就是无法销毁驻留在内存中的元素

function closure(){
    var oDiv = document.getElementById('oDiv');//oDiv用完之后一直驻留在内存中
    oDiv.onclick = function () {
        alert('oDiv.innerHTML');//这里用oDiv导致内存泄露
    };
}
closure();
//最后应将oDiv解除引用来避免内存泄露
function closure(){
    var oDiv = document.getElementById('oDiv');
    var test = oDiv.innerHTML;
    oDiv.onclick = function () {
        alert(test);
    };
    oDiv = null;
}