JavaScript 里边的闭包和 js 垃圾回收——0515

本文详细解析了JavaScript中闭包的概念、作用与弊端,通过多个示例帮助理解闭包的形成与应用,同时探讨了内存泄漏与垃圾回收机制,以及如何有效避免闭包导致的性能问题。

一、闭包

1、概念

闭包——形成私有作用域,让私有变量存到内存里面,保护私有变量,让其不受外界的影响。(外层函数的调用结果是返回内层函数,即内层函数自执行,或者在外层函数作用域内设置返回值为内层函数,这个内层函数就形成闭包,调用外层函数的变量(此时已成为它的私有变量,存到内存里,不受外界影响),形成私有作用域)
闭包的作用:使在外层作用域下可以访问到内层作用域下的变量
闭包的弊端:不建议大量使用闭包,闭包的变量存到内存里边,会导致内存泄漏,或者页面的性能问题。
解决方案:在闭包使用完之后将私有变量删除delete(适用于IE浏览器),或将闭包对应的变量置空(适用于大多数现代浏览器),如下例中的 result=null;,或移除私有变量remove。

什么是内存泄漏?
内存泄漏是指内存资源得不到释放 && 失去对该内存区的指针 => 无法复用内存资源,最终导致内存溢出。
简单来说就是:不再用到的内存,没有及时释放。

什么是js垃圾回收机制?
通常情况下,垃圾回收分为手动回收和自动回收。
手动回收策略,何时分配内存、何时销毁内存都是由代码控制的。
自动回收策略,产生的垃圾数据是由垃圾回收器来释放的,并不需要手动通过代码来释放。

JavaScript 中调用栈中的数据回收

JavaScript 引擎会通过向下移动 ESP(记录当前执行状态的指针) 来销毁该函数保存在栈中的执行上下文。

JavaScript 堆中的数据回收

在 V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。

新生区通常只支持 1~8M 的容量,而老生区支持的容量就大很多了。对于这两块区域,V8 分别使用两个不同的垃圾回收器,以便更高效地实施垃圾回收。
副垃圾回收器,主要负责新生代的垃圾回收。
主垃圾回收器,主要负责老生代的垃圾回收。

点击 Chrome 浏览器垃圾回收机制与内存泄漏分析 了解更多。

2、理解闭包

以下是三个简单的闭包,帮助理解闭包的含义:
示例一:

function fn1() {
    var b = 234;
    function fn2() {    //fn2是一个闭包
        console.log(b);
    }
    return fn2;
}
var result = fn1();
result();   //234  调用result,即执行函数fn2(闭包)
result=null;
//result();   //Uncaught TypeError: result is not a function

说明:result 指向的是fn1的返回值,即函数fn2,声明变量result相当于定义了一个闭包,b为私有变量,存到内存里边,不受外界影响,调用变量result时b的值不会初始化。

示例二:

var add = (function () {
    var count = 0;          //count是私有变量
    return function () {    //这个匿名函数是一个闭包
        count++;
        console.log(count);
    }
})();
add();  //1  add指向的是自执行函数的返回字值,匿名函数,执行add,即执行匿名函数(闭包)
add();  //2
add();  //3

示例三:

 var med="like";
 var fun1=function (){
     console.log(med);  //undefined 变量的声明提前
     var med="windows";     //私有变量
     return function (){     //闭包
         console.log(med);
         med+=" more";
     }
 };
 var ab=fun1();   //相当于将变量ab定义为上边的闭包
 fun1()();  //windows   第一次调用时局部变量med(也是闭包的私有变量)会存到堆内存里,输出结果为windows
 fun1()();  //windows   第一次执行完fun1时堆内存会被释放,第二次相当于重新执行fun1,形成新的堆内存,所以局部变量med会被初始化
 fun1()();  //windows
 ab();    //windows
 ab();   //windows more   调用的是闭包,fun1只执行一次,所以局部变量med不会被初始化,会累加
 ab();   //windows more more

反复调用外部的函数,相当于在重置闭包的变量。

3、闭包案例

案例一:

function fun2(n,o){
    console.log(o);
    var md=n;
    return{
        fun2:function(m){
            return fun2(m,md);
        }
    };
}
var a = fun2(0);  a.fun2(1);  a.fun2(2);  a.fun2(3);   //undefined 0 0 0
var b = fun2(0).fun2(1).fun2(2).fun2(3);               //undefined 0 1 2
var c = fun2(0).fun2(1);  c.fun2(2);  c.fun2(3);      //undefined 0 1 1

注意:

1、函数形参没传值,形参值undefined
2、上例里面存在闭包,也存在对象之间的调用,要注意是对象点,还是对象连点
3、对象点,局部变量会初始化,因为函数执行完,它的执行上下文会被回收,对象点相当于执行完函数,第二次再重新执行。
4、对象连点,局部变量不会初始化,因为对象连点表示函数一直处于执行中,执行上下文不会被回收,直至最后一个点执行完成。

案例二:

var res = [];
for (var i = 0; i < 10; i++) {
    var add1 = function () {
        return function () {
            console.log(i); //当前函数内部输出的变量不是私有变量,而是常规的局部变量,会受外界的影响
        }
    };
    res[i] = add1();  //数组指向返回的匿名函数
}
//在这里运行的时候for循环已经执行完了,所以输出i,全是10
res[0]();
res[1]();

上例不是闭包,输出数组里边的任何一个函数执行都是10

var res=[];
for(var i=0;i<10;i++){
    var add=function(){
        return function(){
            console.log(i);
        }
    };
    res[i]=add();
    res[i]();
}

在for循环内部执行res[i](),返回的结果是0——9,因为每循环一次,就立即执行返回的匿名函数

var res=[];
for(var i=0;i<10;i++){
    var add=function(n){
        return function(){
            console.log(n)
        }
    };
    res[i]=add(i);
}
res[0]();  //0
res[1]();   //1
res[2]();  //2

改为闭包后如上所示,返回函数的执行结果不受for循环影响

案例三:
for循环、计时器的闭包:

for(var i=0;i<10;i++){
    setTimeout(function (){
        console.log(i);  //10个10
    },0);   //这个计时器是延迟0ms,去执行内部的匿名函数
}

js 是单线程的
setTimeout在当前执行队列的最后执行,所以获取到的i是最外层作用域的i=10
若要获得0——9,需要在for循环内创建闭包

改为闭包:

for (var i=0;i<10;i++){
    (function (n){
        setTimeout(function (){
            console.log(n);  //0——9
        },1000);
    })(i);
}

将 i 作为实参,传入自执行函数,从而变为 setTimeout 里边的自执行函数的私有变量,让其不受外部for循环的影响。

案例四:
事件的闭包:

<button class="btn">1</button>
<button class="btn">2</button>
<button class="btn">3</button>
<button class="btn">4</button>

<script>
var btn=document.querySelectorAll(".btn");
    for(var i=0;i<btn.length;i++)
    {
        (function (n){
            btn[n].onclick=function (){
                console.log(n);
            }
        })(i);
        
        //自动触发对象的事件,加上下边这行代码,可以不使用闭包,点击输出对应的0 1 2 3
        //btn[i].click();
    }
</script>

上例如果不使用闭包,输出的结果全部是4,因为事件是被动执行的,事件触发时for循环已执行完毕,所以输出结果为4。
加上闭包之后就会将 i 变为私有变量,存到内存里,点击时输出对应的 i 即0 1 2 3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值