多前端同学可能觉得这是浏览器或引擎该操心的事, 但理解内存管理能帮你写出更高效的代码, 还能避免各种内存泄漏的坑.
一、常见的内存泄露场景
- 意外的全局变量:
javascript
function leaky() {` leak = `'这是一个全局变量'; // 本意是 let leak = ...
` `this.anotherLeak = '这也是全局的'; // 在非严格模式下,this 指向 window`}`
// 解决方法:使用严格模式 "use strict"
- 被遗忘的定时器和回调:
javascript
// 定时器泄漏let someData = getHugeData();
setInterval(() => {` `let node = document.getElementById('node');` `if(node) {` node.`innerHTML = JSON.stringify(someData);` }``}, `1000);
// 即使移除 DOM 节点,定时器还在引用 someData 和 node
javascript
// 事件监听泄漏function setup() {` `let btn = document.getElementById('myButton');` btn.`addEventListener('click', onClick);`}`
function onClick() {` `console.log('clicked!');`}`
// 如果 btn 被移除但没解绑事件,相关内存无法释放
- 闭包的不当使用:
javascript
function outer() {` `let bigArray = new Array(1000000).fill('*');
` `return function inner() {` `console.log('inner');` `// 即使不用 bigArray,它也会被保留,因为闭包保留了整个作用域链` };``}`
let hold = outer(); // bigArray 无法被回收
- DOM引用未清理:
javascript
let elements = {` `button: document.getElementById('myButton'),` `div: document.getElementById('myDiv')`};`
function removeDiv() {` `document.body.removeChild(elements.div);` `// 虽然从 DOM 移除了,但 elements.div 仍引用着这个节点`}`
二、内存优化技巧
- 合理使用变量作用域:
javascript
// 不好的做法function process(data) {` `let result = [];
` `// 大数组处理...` `for(let i = 0; i < data.length; i++) {` result.`push(data[i] * 2);` }`
` `// 后续只使用 result 的前10项` `return result.slice(0, 10);` `// 但整个 result 数组会保留在内存中`}`
// 更好的做法function processBetter(data) {` `let result = [];
` `for(let i = 0; i < 10; i++) { // 只处理需要的部分` `if(i < data.length) {` result.`push(data[i] * 2);` }`` }`
` `return result; // 内存占用更小`}`
- 及时清理引用:
javascript
// 定时器let timer = setInterval(() => {...}, 1000);// 不需要时clearInterval(timer);
// 事件监听function cleanUp() {` btn.`removeEventListener('click', onClick);`}`
// WeakMap/WeakSet 允许对象被垃圾回收let weakMap = new WeakMap();let obj = {};`weakMap.`set(obj, 'some value'); // 如果 obj 被回收,weakMap 中的条目自动消失
- 分块处理大数据:
javascript
// 处理大数组时不要一次性处理function processLargeArray(array, chunkSize, callback) {` `let index = 0;
` `function next() {` `let chunk = array.slice(index, index + chunkSize);` index += chunkSize;`
` `if(chunk.length) {` `callback(chunk, next);` }`` }`
` `next();`}`
// 使用processLargeArray(hugeArray, 1000, (chunk, next) => {` `// 处理当前块` `processChunk(chunk);
` `// 使用 setTimeout 或 requestIdleCallback 给浏览器喘息机会` `setTimeout(next, 0);`});`
-
使用对象池: 频繁创建销毁对象? 试试对象池:
class ObjectPool { constructor(createFn) { this.createFn = createFn; this.pool = []; }
get() { return this.pool.length ? this.pool.pop() : this.createFn(); }
release(obj) { // 重置对象状态(如果需要) this.pool.push(obj); }}
// 使用let particlePool = new ObjectPool(() => ({ x: 0, y: 0, vx: 0, vy: 0}));
// 需要时let particle = particlePool.get();// 使用完后particlePool.release(particle);