JavaScript内存泄露原因及解决方案

在 JavaScript 中,内存泄漏通常发生在不需要的内存没有被垃圾回收器释放时。以下是常见的几种情况:

1. 意外的全局变量

// 意外的全局变量
function foo() {
  bar = "这是一个全局变量"; // 没有使用 var/let/const
}

function baz() {
  this.accidentalGlobal = "这也是全局变量"; // 在非严格模式下,this 指向全局对象
}
baz();

解决方案:

// 使用严格模式
"use strict";

function foo() {
  let bar = "局部变量"; // 使用 let/const
}

2. 被遗忘的定时器和回调函数

// 未清理的定时器
let data = getData();
setInterval(() => {
  let node = document.getElementById('Node');
  if(node) {
    node.innerHTML = JSON.stringify(data);
  }
}, 1000);

// 未移除的事件监听器
const button = document.getElementById('button');
button.addEventListener('click', onClick);

// 页面卸载时没有移除监听器

解决方案:

// 清理定时器
const intervalId = setInterval(callback, 1000);
// 需要时清理
clearInterval(intervalId);

// 移除事件监听器
const button = document.getElementById('button');
button.addEventListener('click', onClick);
// 需要时移除
button.removeEventListener('click', onClick);

3. DOM 引用

// 保留对 DOM 元素的引用
let elements = {
  button: document.getElementById('button'),
  image: document.getElementById('image')
};

// 即使从 DOM 中移除了元素,仍然在内存中保留引用
function removeButton() {
  document.body.removeChild(document.getElementById('button'));
  // elements.button 仍然引用着已移除的 DOM 元素
}

解决方案:

// 及时清理引用
function cleanUp() {
  elements.button = null;
  elements.image = null;
}

4. 闭包

// 闭包导致的内存泄漏
function createClosure() {
  let largeArray = new Array(1000000).fill('*');
  
  return function() {
    console.log(largeArray.length);
    // largeArray 一直被闭包引用,无法被回收
  };
}

const closure = createClosure();

解决方案:

// 及时释放闭包引用
function useClosure() {
  const closure = createClosure();
  // 使用完毕后释放
  closure = null;
}

5. 缓存对象

// 无限增长的缓存
const cache = {};
function setCache(key, value) {
  cache[key] = value;
}

// 没有清理机制,缓存会无限增长

解决方案:

// 使用有大小限制的缓存
class LimitedCache {
  constructor(maxSize = 100) {
    this.maxSize = maxSize;
    this.cache = new Map();
  }
  
  set(key, value) {
    if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }
}

6. 分离的 DOM 节点

// 从 DOM 树中移除但仍在 JavaScript 中引用的节点
let detachedTree;
function create() {
  const ul = document.createElement('ul');
  for(let i = 0; i < 10; i++) {
    const li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul; // 保留引用但未添加到 DOM
}

create();
// detachedTree 引用的整个 UL 树都无法被回收

7. 事件监听器在组件销毁时未移除

// 在单页应用中常见的问题
class Component {
  constructor() {
    this.handleResize = this.handleResize.bind(this);
    window.addEventListener('resize', this.handleResize);
  }
  
  handleResize() {
    // 处理逻辑
  }
  
  // 缺少销毁方法,事件监听器会一直存在
}

// 正确的做法
class SafeComponent {
  constructor() {
    this.handleResize = this.handleResize.bind(this);
    window.addEventListener('resize', this.handleResize);
  }
  
  destroy() {
    window.removeEventListener('resize', this.handleResize);
  }
}

预防内存泄漏的最佳实践

  1. 使用严格模式防止意外的全局变量
  2. 及时清理定时器和事件监听器
  3. 避免不必要的全局变量
  4. 在组件销毁时清理所有引用
  5. 使用弱引用(WeakMap、WeakSet)当需要时
  6. 定期进行内存分析使用开发者工具
// 使用 WeakMap 避免内存泄漏
const weakMap = new WeakMap();
let domNode = document.getElementById('node');
weakMap.set(domNode, 'some data');

// 当 domNode 被移除时,WeakMap 中的条目会自动被垃圾回收

通过遵循这些实践,可以显著减少 JavaScript 应用中的内存泄漏问题。

posted @ 2025-10-11 14:53  阿木隆1237  阅读(14)  评论(0)    收藏  举报