function sf() {
var num = 0
return function inner() {
console.log(num)
}
inner()
}
var fn = sf()
fn()
fn()
fn()
把一个内部的函数,通过return的形式,拿到了全局范围,然后就变成了全局函数。本来num是一个局部变量,本来它被用完了应该被清理,但是现在不清理,这个num变量会一直存在,一直到全部的调用都消失了,才会清理。
这种情况就涉及到了垃圾回收机制的问题
画图说明更直观:
垃圾回收机制 - 王云飞去餐厅吃饭,吃完饭餐桌肯定弄的乱七八糟。
一种情况是按照要求,所有吃完饭的人,走之前你必须给我把桌子给我收拾干净,不然不能走。
另一种,你尽管吃,别管多乱,吃完你就走,旁边会有一个专门负责打扫卫生的保洁,然后他没事就在这里转悠,她发现这个桌子吃完了,然后清理掉。这个充当的就是垃圾回收器的角色。简称为GC。
很显然它是自动的不用你管我们的JS也是这样的,所以当一个变量没有用清理的时候你不必担心,该清理的时候自然就清理了。
垃圾回收机制的常见算法
引用计数法(浏览器):
用的较多
计数法的意思就是给每个变量贴标签,只要有一个人用我就给他写个数字:1,两个人用我就写:2,三个人用就是:3;其中有一个人不用了我可以把技术撤掉,把3改回2。总之就是上面有个数字,代表几个人在使用。
这对于GC来说就简单了,它巡视查看内存的时候只要看这个变量的计数是不是等于0,如果等于0毫不犹豫的清理掉,不等于0说明还有人在使用就不能清理。
这种方式容易产生很多碎片,有些空间能用有些被占用了,不会连续,我们说硬盘里时不时要做碎片整理,就是为了让我们硬盘空间利用效率更高,所以说最好就是时不时的把空间规整一下,排个序。
复制整理法(Java内存管理):
不停在用,整个过程都不清理。一口气把所有的已经占用对的空间复制一遍,它在整个复制的过程中把所有的内存扫描了一遍,复制的时候把没用的清理了,而且做了重新排序。
缺点:你的内存必须得一分为二,可能有一半空间不能利用。如果你内存大不在乎,这种方式是不错的选择
标记清除法(浏览器):
用的不多
不停的增加,每隔一段时间就使劲的扫描内存,并且清理,过一段时间再扫描,和引用计数法一样,同样会存在碎片
标记整理法(浏览器):
用的较多
当变量越来越多的时候,它也会定期的进行扫描,扫描完了它从头到尾做了一个排序,一边清除,一边排序,工作量蛮大的
闭包中的变量num为什么不会被回收?
明白了垃圾回收机制,那么闭包中的num为什么不会被回收,应该就可以解释了吧!外面的全局函数还在使用num,因此num身上有计数,不为零,所以它不会被清除。
内存泄漏常见情况
- 闭包写多了就会造成内存泄漏,一般不可能代码里全是闭包,我们只能说有这个风险
- 大量的使用全局变量,现在基本不会出现,因为我们现在都模块化了,全局变量很少用
- 定时器,这个可能性大一些,我们总是开启了就忘记了关闭,经常犯
- DOM元素的引用:
<body>
<button id = 'btn'>按钮</button>
</body>
<script>
var btn = document.getElementById('btn')
btn.remove() // 这个按钮就不会出现在页面上了
// 但是你在控制台(console.log())输入btn ,结果就是还有
// 为什么还有?
// 页面是不存在了,但是页面上还有
// 就是因为全局变量 btn ,btn.remove删除了节点,但是它就还被保留在内存当中
//解决方案:
</script>
解决方案
btn = null // 现在被释放了
网友评论