内存泄漏和闭包可以说是最基本的内容了,但要清楚他们之间的的关系,则得先了解什么是垃圾回收。
1. 垃圾回收
那什么是垃圾呢?一直以来也没有明确的定义,但都形成了一个共识,那就是不再需要的内存
,这些内存里的数据就是垃圾
。那什么又是不再需要呢?这个是由开发者决定的,需不需要取决于你。
比如说:
function createIncrease() {
const doms = new Array(100).fill(0).map((_, index) => {
const dom = document.createElement("div");
dom.innerHTML = index;
return dom;
});
return function increase() {
doms.forEach((dom) => {
dom.innerHTML = Number(dom.innerHTML) + 1;
});
};
}
const increase = createIncrease();
document.querySelector("button").addEventListener("click", increase);
这个createIncrease
创建了100
个div
,原来的div
的内容是下标,从0开始。里面有个子函数increase
,循环所有的div
把内容+1
。
这个很标准的闭包了,拿到子函数,也拿到了上面声明的doms
,从运行过程来看,每次点击按钮,使用这个子函数
时,是需要这个doms
的。所以这些doms
是不是垃圾?很明显不是,因为我要使用它。垃圾与占用内容空间的大小无关。
再比如说:
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((total, num) => total + num, 0);
console.log('sum',sum);
对数组进行求和,第一次运行结束之后,那[1, 2, 3, 4, 5]
是需要的还是不需要呢?是结束了,还是后面还要用到?所以回到上面,这取决于你。
所以说,现在有一些你认为的不再需要的内容,那它们就是垃圾
。js语言里面有个垃圾回收器
,它可以帮助我们回收那些不再需要的内容。😂问题又来了,它怎么知道我们不再需要了。嘿,连我们自己都不知道,它知道就离谱了。虽然它不知道,但是啊,它还是很大胆的,它做出假设,虽然不知道你需要哪些东西,但是你访问不了的东西,你肯定是不想要的。比如上面的例子,
let arr = [1, 2, 3, 4, 5];
arr = [4, 5, 6, 7, 8, 9];
const sum = arr.reduce((total, num) => total + num, 0);
console.log("sum", sum);
对arr
重新赋值,那初始化的那内容空间还有机会访问吗?没了,不可能访问到了,所以回收机制就认为这个就是你不需要的。
所以垃圾回收器
回收的是那些我们认为不想要的,且无法触达的内存空间。那不想要的有很多,无法触达的知识其中的一小部分,还有一部分是可以触达的,所以垃圾回收器
无法回收,它们就一直存在那里,这就是内存泄漏
。
那当这个内存泄漏变大了之后,就会严重影响我们程序的运行,那如何解决内存泄漏
?就是让这些内容变得不可触达
就可以了。比如说,
let arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((total, num) => total + num, 0);
console.log("sum", sum);
arr = null;
在程序运行结束后,令arr = null
,那[1, 2, 3, 4, 5];
内存空间就变得不可触达,就能被垃圾回收器
处理了。
又比如第一个,点击第一次之后就不需要了。
const btn = document.querySelector("button");
let increase = createIncrease();
function handle() {
increase();
btn.removeEventListener("click");
increase = null;
}
btn.addEventListener("click", handle);
第一次点击过后,就移除监听器,然后令increase = null
,那之前的函数就变得不可触达,然后被回收。
哎,说到这里,怎么都是垃圾回收和内存泄漏,那内存泄漏和闭包的关系呢?从上面的例子看,好像没有什么直接的关系,只是闭包会让我们放松警惕。像例2,只需要在最后令arr = null
,就可以了。但是闭包得绕一下,你只是认为以后不用这个函数了,函数能有多大的内存空间,所以就不理了。😳但是闭包所关联的那个环境,把doms
保留下来了。所以不是流传着那句话闭包容易引起内存泄漏,慎用闭包
嘛。
闭包的内存泄漏和其他内存泄漏没有啥本质的区别:
1.持有了不再需要的函数引用,会导致函数关联的词法环境无法销毁,从而导致内存泄漏。
2.当多个函数共享词法环境时,会导致词法环境膨胀,从而导致无法触达也无法回收的内容空间。
function createIncrease() {
const doms = new Array(100).fill(0).map((_, index) => {
const dom = document.createElement("div");
dom.innerHTML = index;
return dom;
});
function increase() {}
function tem() {
doms;
}
return increase;
}
const btn = document.querySelector("button");
let increase;
btn.addEventListener("click", () => {
increase = createIncrease();
});
点击按钮,但是这个函数没有用到闭包环境里的doms
,doms
被另一个子函数使用了。点击按钮后,在控制台手动垃圾回收
,再比较前后的内存,发现内存没被回收。
这就是因为tem
函数的存在,它们的闭包环境都是一样的,共享一个词法环境。如果说只有一个子函数,那如果没有用到,浏览器就会把doms
优化掉,这就不会造成内存泄漏。但是现在有另一个函数使用了doms
,那浏览器不敢回收。后面返回increase
,没用到doms
,但是词法环境仍然保留了这个doms
。
原文链接:https://juejin.cn/post/7343255545811796004 作者:初出茅庐的