垃圾回收与内存泄漏
浏览器的 JavaScript 具有自动垃圾回收机制,原理是:垃圾收集器会定期(周期性)找出那些不再继续使用的变量,然后释放其内存
定期:这个过程不是实时的
不再继续使用的:只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在
标记无用变量的策略通常有两种方式:标记清除(主流)和引用计数
标记清除
当变量进入环境时,就将这个变量标记为「进入环境」;反之则标记为「离开环境」
目前为止 IE9+、FireFox、Opera、Chrome、Safari 使用的都是标记清除策略,区别在于垃圾收集的时间间隔不同
引用计数
跟踪记录每个引用类型的值被引用的次数,当垃圾回收器再次运行时,它就会释放那些引用次数为 0 的值所占用的内存
但是存在循环引用的问题,就是两个对象分别具有对方的引用,这两个变量就不会被垃圾回收器回收
闭包会影响垃圾回收,造成内存泄漏?
不恰当的闭包使用可能会导致一些父作用域中的变量无法被回收
当一个函数返回一个匿名函数,这个匿名函数使用到了这个函数的局部变量(形成闭包)时,就会阻止了这个局部变量被垃圾回收
function createCounter() {
let count = 0; // `count` 是 `createCounter` 的局部变量
return function() {
count += 1;
console.log(count);
};
}
const myCounter = createCounter(); // `createCounter` 执行完毕后,通常其局部变量 `count` 应该被回收
// 但因为返回的函数形成了一个闭包,它持有 `count` 的引用,所以 `count` 不会被立即回收
myCounter(); // 输出:1
myCounter(); // 输出:2如何解决
当不再需要 myCounter 时,要置为 null
内存泄漏有哪些情况
- 未被清理的定时器和回调函数
当使用 setInterval
或 setTimeout
创建定时器时,即使对应的元素或组件已经不存在,如果没有明确地清除这些定时器,它们仍然会继续运行,造成内存泄漏。
- 未取消的事件监听器
对元素添加的事件监听器,如果在元素被移除或不再需要时没有被移除,那么这些事件监听器会保持对元素的引用,阻止了元素的回收。
- 悬浮引用
当 DOM 元素被移除或替换时,如果存在 JavaScript 代码仍保持对这些元素的引用(比如通过变量或者集合),则这部分内存不能被回收。
- 闭包
如之前讨论的那样,不恰当的闭包使用可能会导致一些父作用域中的变量无法被回收。
排查
通过开发者工具中的Performance
模块,可以对页面加载和执行流程进行录制,之后在录制快照里查看内存的使用情况。如果发现有内存异常,则可以进一步缩小排查范围,最终找到引发内存泄露的代码。