什么是内存泄露?
本质上,内存泄露可以定义为:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收。编程语言管理内存的方式各不相同。只有开发者最清楚哪些内存不需要了,操作系统可以回收。一些编程语言提供了语言特性,可以帮助开发者做此类事情。另一些则寄希望于开发者对内存是否需 要清晰明了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
const arr = [1, 2, 3, 4];
console.log('hello world');
如果增加一行代码,解除arr对[1, 2, 3, 4]引用,这块内存就可以被垃圾回收机制释放了。
let arr = [1, 2, 3, 4];
console.log('hello world');
arr = null;
上面代码中,arr重置为null,就解除了对[1, 2, 3, 4]的引用,引用次数变成了0,内存就可以释放出来了。
因此,并不是说有了垃圾回收机制,开发者就轻松了。你还是需要关注内存占用:那些很占空间的值,一旦不再用到,你必须检查是否还存在对它们的引用。
如果是的话,就必须手动解除引用。
常见的 JavaScript 内存泄露
1:意外的全局变量
JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。
function foo(arg) {
bar = "this is a hidden global variable";
}
真相是:
function foo(arg) {
window.bar = "this is an explicit global variable";
}
另一种意外的全局变量可能由 this 创建:
function foo() {
this.variable = "potential accidental global";
}
foo();
启用严格模式解析 JavaScript避免意外的全局变量。在 JavaScript 文件头部加上 'use strict',可以避免此类错误发生。
全局变量注意事项!!!
尽管我们讨论了一些意外的全局变量,但是仍有一些明确的全局变量产生的垃圾。它们被定义为不可回收(除非定义为空或重新分配)。
临尤其当全局变量用于临时存储和处理大量信息时,需要多加小心。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。
与全局变量相关的增加内存消耗的一个主因是缓存。缓存数据是为了重用,缓存必须有一个大小上限才有用。高内存消耗导致缓存突破上限,
因为缓 存内容无法被回收。
2:被遗忘的计时器或回调函数
示例代码:
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// 处理 node 和 someResource
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
所以:与节点或数据关联的计时器不再需要,node 对象可以删除,整个回调函数也不需要了。可是,计时器回调函数仍然没被回收(计时器停止才会被回收)。同时,someResource 如果存储了大量的数据,也是无法被回收的。所以先停止定时器 然后再回收
3:脱离 DOM 的引用
有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成JSON 键值对
或者数组。此时,同样的 DOM 元素存在两个引用,将来你决定删除这些行时,需要把两个引用都清除。
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
// 更多逻辑
}
function removeButton() {
// 按钮是 body 的后代元素
document.body.removeChild(document.getElementById('button'));
}
// 虽然我们用removeChild移除了button, 但是还在elements对象里保存着#button的引用,DOM元素还在内存里面.不能被垃圾回收站回收。
4:闭包
闭包实际上非常容易造成JavaScript对象和DOM对象的隐蔽循环引用。
代码示例:
function example(){
var element =document.getElementByID("div1"); //①
element.onclick = function() {
alert("This is a leak!"); //②
}
}
以上函数example() 中用匿名函数创建了一个闭包。
第①句: JS(element) ->DOM(div1)>
第②句: DOM(div1.onclick) ->JS(element)>
由此形成了JavaScript对象和DOM对象的隐蔽循环引用。
2.解决方法:
常用的解决方法就是在JavaScript代码段运行完之时将形成循环引用的JavaScript对象手动设置为空,切断引用。
修改的例子如下:
[javascript] view plain copy print?
function example(){
var element =document.getElementByID("div1"); //①
element.onclick = function() {
alert("This is a leak!"); //②
}
element = null; //添加的语句
}
</body>
</html>