JS作用域、闭包、闭包引发的内存泄漏问题

作用域:

所谓作用域是指函数能够影响的范围,通常在这个范围内存在着各种常量、变量、函数等。同时在JS中还存在着作用域链,就是在一个作用域中包含着其他作用域。

例如:

    function NewFun() {
        var a = 0;
        console.log(a);
    }
    NewFun();
    console.log(a);

上述代码在访问变量a时,只有在NewFun方法的作用域中打印a才能正确打印值,而在NewFun函数的作用域范围外,则会提示存在a这个变量,这就是JS的函数作用域。

输出结果:

在ES6版本之前,JS只有全局作用域和函数作用域,在ES6版本中引入了块级作用域。同时引入了两个支持块级作用域的变量类型let 和 const

注:var不支持块级作用域,只支持全局和函数作用域。

最常见的块级作用域是for循环体中的条件变量

在老版本中定义for循环如下:

    for (var i = 0; i <= 5; i++) {
        console.log(i);
    }
    console.log('外部访问:' + i);

输出结果:

我们发现在外部仍然能够访问到i的值,在大多数情况下,我们不希望在循环结束后依旧能够访问到i的值,希望它能够被系统回收,所以在ES6中利用let支持块级作用域的情况下循环可以改写为:

    for (let i = 0; i <= 5; i++) {
        console.log(i);
    }
    console.log('外部访问:' + i);

 输出结果为:

我们可以发现i并未定义,所以利用块级作用域,我们可以很好的回收掉变量i。

闭包 :

所谓闭包就是指函数能够访问到外部的变量,反之则不成立,例如

 let c = -1;

    function NewFun() {
        let b = 0;
        function Fun2() {
            let a = 2;
            console.log(a, b, c);
        }
        Fun2()
    }
    NewFun()

Fun2函数中可以访问到NewFun中的b变量和全局中的c变量,这就是闭包。

通过对闭包和作用域的了解,我们可以推断出几个函数之间还存在着某种联系,我们称这种联系为作用域链。

我们把上述代码改写为:

 let a = -1;
    function NewFun() {
        let a = 0;
        function Fun2() {
            let a = 2;
            console.log(a);
        }
        Fun2()
    }
    NewFun()

此时我们可以将函数之间的关系绘制成一张图:

从全局----->NewFun---->Fun2的这条线路就是作用域链,当Fun2输出变量在自己作用域中找不到时就会顺着这条作用域链向上查找,直至找到这个输出的变量。

由闭包引发的内存泄漏问题

在老版本中的浏览器中由于垃圾回收机制是引用计数器,使用闭包特性引用外部变量,会使得变量计数器加1,计数器不为0,所以JS垃圾回收始终不会回收掉该变量,使得无用的变量在内存中越来越多,从而引发内存泄漏。

在下列代码中:

<body>
    <button>1</button>
    <button>1</button>
    <button>1</button>
</body>
<script>
    function SetBtnClick() {
        let btns = document.querySelectorAll('button');
        btns.forEach(btn => {
            btn.addEventListener('click', function() {
                btn.innerHTML = Number(btn.innerHTML) + 1;
            })
        })
    }
    SetBtnClick()
</script>

我在设置按钮的监听事件中利用闭包特性引用了外部的btn变量获取了innerHTML的值,所以由于JS的垃圾回收机制,btn被点击时引用了,所以垃圾机制回收不掉btn这个变量,又因btn存在与SetBtnClick---->btn.forEach中的回调函数---->btn监听回调函数这条作用域链上,导致整条作用域链的计数器都不为0,没有被回收,若存在着许多这样没有被回收掉的作用域,则会照成内存拥堵,从而引发内存泄漏。

解决方法:

   function SetBtnClick() {
        let btns = document.querySelectorAll('button');
        btns.forEach(btn => {
            let this_btn = btn;
            btn.addEventListener('click', function() {
                this_btn.innerHTML = Number(this_btn.innerHTML) + 1;
            })
            btn = null;
        })
    }
    SetBtnClick()

我们将foreach循环的btn赋值给临时变量this_btn,同时在操作按钮对象时获取对象的任何属性都从临时变量中获取,同时在btn使用完成后,手动清除btn变量,从而解决内存变量的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值