欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

关于闭包的变量存储的问题

程序员文章站 2022-06-28 18:15:55
前段时间和朋友在探讨闭包内的变量存储的问题时,发现我们对闭包内的变量存储模糊不清,所以近段时间看了一些关于闭包存储的文章,在这里打算记录一下。闭包,这个概念对于每位JSer而言都不陌生,它几乎伴随着每个前端入门者的初学阶段,重要到几乎每家公司面试都会问。关于闭包究竟是什么,闭包干嘛用的,网上各种回答也是五花八门,动不动就扯到隐匿变量、内存泄漏这些概念,让没有点基础的初学者越看越晕,我不能说那些是错的,不过显然对新手不太友好。//变量(内存)销毁看过红宝书的都见过这个词,不过大神的例子我觉得还是有....

前段时间和朋友在探讨闭包内的变量存储的问题时,发现我们对闭包内的变量存储模糊不清,所以近段时间看了一些关于闭包存储的文章,在这里打算记录一下。

闭包,这个概念对于每位JSer而言都不陌生,它几乎伴随着每个前端入门者的初学阶段,重要到几乎每家公司面试都会问。
关于闭包究竟是什么,闭包干嘛用的,网上各种回答也是五花八门,动不动就扯到隐匿变量、内存泄漏这些概念,让没有点基础的初学者越看越晕,我不能说那些是错的,不过显然对新手不太友好。

//变量(内存)销毁
看过红宝书的都见过这个词,不过大神的例子我觉得还是有点晦涩,我来举个更简易的例子——现在有一个button和一个有数字的span,button负责让数字加1,代码如下:

<body>
    <button id="add">1</button>
    <span id="span">10</span>
</body>
<script>
    var a = 10;
    add.onclick = function (){
        a++;
        span.innerHTML = a;
    }
</script> 

简单到不能再简单了吧,我每次按add按钮,就会让数字加一。
现在把js部分做个改变:

<script>
add.onclick = function (){
var a = 10;
a++;
span.innerHTML = a;
}
</script>

聪明的你想必已经看出来了,将声明变量a这个步骤从外面挪到onclick事件函数里面的话,不管你怎么按a将永远保持11,页面的span里的数字也将永远是11,这是因为每次按按钮的时候,我都重新声明了一个变量a,等于每次都重置为10了啊,每次++只能是11然后无限循环。
那么为什么声明a这一步写在函数外面就行了?
我最初认为a一开始只声明了一次,每次++的a都是在旧的a上面进行操作。这个回答不能说错,但显然没有进行深入的思考,一开始我百思不得其解,后来通过这个点击的例子就明白了——每次点击按钮,函数执行完毕后,这个a就被当成没有用的变量被回收了,或者换句话说,内存被销毁了!所以a始终都是初始的那个10!那么让我们回到原点。

闭包的示例

先来看看一个闭包的实例:

function makeStevenFunc() {
    var stevenx911_name = new Array(1000000).join('x'); //这里通过数组构造一个1MB大小字符串

    function displayStevenName() { // 这里定义一个具名函数,方便我们查找
        console.log(stevenx911_name);
    }
    return displayStevenName;
}

var myFunc = makeStevenFunc();
myFunc();

这是一段闭包的代码,在谷歌浏览器中运行下的结果是这样的:
关于闭包的变量存储的问题
这里说明下,heapdump是对堆内存进行文件快照,从上述的结果中可以清晰地看到闭包中定义的基础类型string变量stevenx911_name存放在堆中,所以我们可以确认闭包中的变量会存在堆(heap)中。
但是makeStevenFunc定义的所有变量都会放进闭包吗?我们改下代码继续看:

function makeStevenFunc() {
    var stevenx911_name = new Array(1000000).join('x'); 
    var stevenx911_desc = new Array(1000000).join('y'); // 增加这句,同样构造一个1MB大小字符串
    function displayStevenName() {
        console.log(stevenx911_name);
    }
    return displayStevenName;
}

var myFunc = makeStevenFunc();
myFunc();

我们再来看看运行的结果:
关于闭包的变量存储的问题
答案似乎很明显,heapdump的size没有变化,示例代码形成的闭包并没有包含父函数中定义的所有变量,仅仅将子函数引用到的变量放置了进来。
到这里,关于闭包中的变量存储位置就基本清楚了!细心的同学一定会发现,heapdump的操作是在上述代码执行结束时做的,但闭包中的变量空间并没有释放,垃圾回收器似乎无法回收,所以就引出了闭包带来的问题:内存泄漏。

闭包引起的内存泄漏

在js中,闭包是内存泄漏发生的重灾区,所以在使用闭包时请务必小心,多做测试,我们来看一段引起内存泄漏的示例代码:

var funs = [];
function fun0(){
    funs.push(getVar());    //外部变量持有闭包的引用
}
setInterval(fun0, 1000);
function getVar(){
    var arr = new Array(1000000);
    return function(){
        console.log(arr);
    }
}

在浏览器中执行这段代码后,我们观察Proformance Monitor可以看到JS Heap Size持续增长,内存占用不断上升,因为外部变量(全局)持有闭包的引用,如果发生在页面上,当内存用量耗尽时,页面就会卡死。解决的方法可以将内存分配操作(new)置于闭包之外,或者用完及时销毁(赋值为null)。
关于闭包的变量存储的问题
当然现实情况可能不是定时器,而多数是用户的操作,比如onclick和onresize等频繁被触发的事件回调。

总结
关于闭包中的变量存储,网络上已经很多文章来介绍,示意图很多,理论也很详实,如果想要了解更多,可以打开本文参考部分的引用链接进行深入阅读,另外,我认为在学习js语言时:ECMA规范说明是第一权威,其次是规范的实现——js引擎,再次就是各路视频博客了,所以本文旨在从运行时的角度让大家在引擎层面对闭包中的变量存储有一些浅层的认识。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
https://developers.google.com/web/tools/chrome-devtools/memory-problems?hl=zh-cn
写的很详细

本文地址:https://blog.csdn.net/weixin_45731450/article/details/109262333

相关标签: javascript