一、前置知识
闭包的产生
js垃圾回收机制
不懂的可以先移步到下方的博客再回来
「前端进阶」JS中的内存管理 - 知乎
二、闭包是否会产生内存泄露
在此强调变量arr保存的是对引用数据类型数组的引用(地址),保存在栈中;数组的每一项值保存在堆中,new Array(10000000)大约占据40mb内存
案例1:
function fn() {
//fn函数作用域的局部变量arr
const arr = new Array(10000000)
function fn1() {
// 在fn1函数作用域下访问(依赖)外层fn函数作用域的局部变量arr,此时会产生闭包
console.log(arr[0]);
}
fn1()
}
fn()
打开控制台进行断点查看:
![](https://img-blog.csdnimg.cn/7ae88c70442c4dfe8da6b6d08239b64b.png)
打开控制台进行性能分析:
![](https://img-blog.csdnimg.cn/45b31861feeb454dbb3794961b1e219a.png)
ps:函数在执行时一开始由于创建了new Array(10000000),所以占据了堆中大概40mb内存,也就是一个Array(10000000)的大小,不过在很快的一段时间内变为了1.3mb,也就是js的垃圾回收机制在闭包函数运行后成功后在一段时间后把闭包产生的内存垃圾(new Array(10000000))给清理掉了
案例二:
function fn() {
const arr = new Array(10000000)
function fn1() {
console.log(arr[0]);
}
return fn1
}
fn()()
打开控制台进行断点查看:
![](https://img-blog.csdnimg.cn/d05a485fd10b4609ab20459177e34f20.png)
打开控制台进行性能分析:
![](https://img-blog.csdnimg.cn/c6fdf42dd16542bfa4f9f95c925d36d3.png)
ps:跟案例1一样,不会造成内存浪费,不多加解释
案例3:
function fn() {
const arr = new Array(10000000)
function fn1() {
console.log(arr[0]);
}
return fn1
}
//变量test由于保存了fn1函数的引用,而fn1函数作用域内又依赖于fn作用域的Array(10000000),
// 所以导致Array(10000000)在堆内存中无法被释放
let test = fn()
test()
打开控制台进行断点查看:
![](https://img-blog.csdnimg.cn/97e08743519648a4a5393d3a954b88dc.png)
打开控制台进行性能分析:
![](https://img-blog.csdnimg.cn/be43fbb11a714d08a67f8ecfaf6443a8.png)
ps:
js垃圾回收机制的一个算法是标记清除法:
一、即能被标记的到的变量,说明变量可能会在某一个时间点需要用到,所以垃圾回收机制不会将该变量进行回收,一直存放在内存中,直到页面浏览器的关闭,变量才会被释放
二、就比如像全局作用域下的变量,整个代码线程运行过程中,全局变量都存在于内存中,因为全局变量不知道在什么时候会再次使用,所以js会使用(window.全局变量)对全局变量进行标记,所以对标记到的全局变量不会进行回收
①在本案例中闭包函数运行时由于创建了 new Array(10000000),所以占据了堆中大概40mb内存,再有全局变量test由于保存了fn1函数的引用,而fn1函数作用域内又依赖于fn作用域的Array(10000000),导致js垃圾回收机制通过全局变量test---》fn1函数---》Array(10000000),从而找到Array(10000000)进行标记,所以js不会对存在堆内存的中Array(10000000)进行回收,导致堆内存中的Array(10000000)(40mb)一直无法释放。从而造成内存浪费。
解决方案:
通过设置变量为null将标记链断开,修改本案例如下:
function fn() {
const arr = new Array(10000000)
function fn1() {
console.log(arr[0]);
}
return fn1
}
//变量test由于保存了fn1函数的引用,而fn1函数作用域内又依赖于fn作用域的Array(10000000),
// 所以导致Array(10000000)在堆内存中无法被释放
let test = fn()
test()
test = null
![](https://img-blog.csdnimg.cn/f42070683515429296bee04550dbe8eb.png)
ps:通过将全局变量test的值设为null,全局变量test无法再依赖fn1函数---》Array(10000000),从而无法对Array(10000000)进行标记,所以js会对存在堆内存的中Array(10000000)进行回收,导致堆内存中的Array(10000000)(40mb)释放。不会造成内存浪费。
案例4:
function fn() {
const arr = new Array(10000000)
function fn1() {
console.log(arr[0]);
return new Array(10000000)
}
return fn1
}
let test = fn()
let arr = test()
打开控制台进行性能分析:
![](https://img-blog.csdnimg.cn/3006eb78b0b04e78a1ea488c3d9677dd.png)
ps:80mb大概就是两个 Array(10000000)的内存大小,因为全局变量test,arr一直保持对Array(10000000)的引用,所以导致js垃圾回收机制无法回收,
解决方案:
设置变量设置为null
function fn() {
const arr = new Array(10000000)
function fn1() {
console.log(arr[0]);
return new Array(10000000)
}
return fn1
}
let test = fn()
let arr = test()
test = null
arr = null
打开控制台进行性能分析:
![](https://img-blog.csdnimg.cn/738349f70d2a4f8caf7ce041692d82e1.png)
ps:经过设置变量为null,从而将内存浪费进行释放
三、总结
1.使用闭包是否一定会产生内存浪费吗?
答:不一定,闭包是否产生内存浪费取决于js的垃圾回收机制是否将变量占用的内存给及时清除掉,常见的垃圾回收算法有标记清除法,能标记到就不回收,反之则回收。一般如果闭包函数没有返回值或者没有使用全局变量对闭包函数的返回值进行存储,那么是不会造成内存浪费的,详见文章的案例1和案例2;而如果闭包函数存在返回值并且使用全局变量对闭包函数的返回值进行存储,那么可能会造成内存浪费的,详见案例3和案例4
2.如何解决闭包产生的内存浪费
通过设置变量为null即可间接或直接释放内存浪费,变量使用完毕后及时设置为null即可