JavaScript中的内存溢出和内存泄露

内存溢出是程序运行时请求的内存超过系统剩余内存,导致程序崩溃。内存泄露则指占用的内存未及时释放,长时间积累可能导致内存溢出。例子包括创建大量大数组导致溢出,以及未清除的全局变量和定时器引起的内存泄露。解决策略包括合理分配和释放内存,避免无限循环和不必要的全局变量,及时关闭计时器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内存溢出

  • 概念:一种程序运行出现的错误,当程序运行需要的内存超过了剩余的内存时,就出抛出内存溢出的错误。
  • 举例:
var obj = {}
for (var i = 0; i < 10000; i++) {
	obj[i] = new Array(1000000000)
}

在这里插入图片描述

内存泄露
概念:占用的内存没有及时释放,内存泄露积累多了就容易导致内存溢出。

  • 常见的内存泄露:
    (1)意外的全局变量
    (2)没有及时清理的计时器或回调函数*闭包
  • 举例:
    (1)意外的全局变量
function fn() {
	a= 3
	console.log(a)
}
fn()
// fn() = null

(2)启动循环定时器后不清理

var intervalId = setInterval(function () {
	console.log( '----')
}, 1000)
// cLearInterval(intervaLId)

(3)闭包,不及时释放

function fn1() {
	var a = 4
	function fn2() {
		console.log(++a)
	}
	return fn2
}
var f = fn1()
f()
// f = nulL
### JavaScript 内存溢出的原因 JavaScript内存溢出通常由以下几个因素引起: - **过多的小对象创建**:频繁创建大量小型对象会占用大量的堆空间,如果这些对象未能及时被垃圾回收机制处理,则可能导致内存泄漏。对于大型应用程序而言,应当关注对象的数量及其大小[^1]。 - **闭包引起的循环引用**:当函数内部定义了另一个函数,并且内层函数访问外层作用域中的变量时就会形成闭包。如果不小心管理,可能会造成不必要的长时间持有对某些数据结构的引用,阻止它们被释放。 - **未解除事件监听器绑定**:动态添加到 DOM 节点上的事件处理器如果没有正确移除,在节点销毁之后仍然会被保留下来,从而占据额外的空间。 - **全局变量滥用**:过度依赖全局命名空间内的变量容易引发冲突以及难以追踪的问题,同时也增加了潜在的风险因为任何地方都可以修改其值而导致意外行为发生。 ### 解决方案 针对上述提到的各种情况可以采取相应措施来预防解决内存溢出问题: #### 减少不必要对象实例化次数 优化代码逻辑减少无谓的对象构建频率;考虑重用已存在实体而不是每次都新建相同类型的实例;利用缓存技术存储常用资源以降低重复加载成本。 ```javascript // 不推荐的做法 function createManyObjects() { const array = []; for (let i = 0; i < 1e6; ++i) { // 创建一百万个新对象 let obj = {}; obj.value = Math.random(); array.push(obj); } } // 推荐做法 class ValueCache { constructor(size) { this._cache = new Array(size).fill(null); // 初始化固定长度数组并填充null作为占位符 this._index = 0; } getNextValue() { if (!this._cache[this._index]) { this._cache[this._index] = {}; // 只有在需要的时候才分配新的对象 } this._cache[this._index].value = Math.random(); // 更新现有对象属性而非重新赋值整个对象 this._index = (this._index + 1) % this._cache.length; // 循环使用预设容量范围内的索引位置 return this._cache[this._index]; } } ``` #### 避免闭包造成的长期引用 通过调整设计模式或者重构算法流程使得不再有必要维持持久性的上下文环境连接关系;确保临时使用的局部状态能够在适当时候得到清理而不会残留于内存之中。 ```javascript // 容易导致内存泄露的例子 const addHandlerWithClosureLeak = () => { document.body.addEventListener('click', function(event){ console.log(window.someGlobalVariableThatShouldBeReleased); // 这里形成了对外部大对象的隐式引用 }); }; // 更好的实践方式 const addHandlerWithoutLeaking = () => { const localReference = window.someGlobalVariableThatShouldBeReleased.slice(); // 复制一份副本供本地使用即可满足需求 document.body.addEventListener('click', function(event){ console.log(localReference); // 使用复制后的版本代替原始的大对象引用 }, false); delete window.someGlobalVariableThatShouldBeReleased; // 明确指示GC该对象已经不需要再保持存活 }; ``` #### 正确管理解绑DOM事件监听器 始终记得为每一个附加给特定HTML元素的动作指定唯一的标识符以便后续能够方便地找到它来进行注销操作;遵循最佳编码习惯只注册真正必要的交互响应程序并且尽可能早地撤销那些不再有用处的手势捕捉功能。 ```javascript // 错误示范——忘记清除旧有的点击回调 document.getElementById('myButton').addEventListener('click', doSomething, false); setTimeout(() => { document.getElementById('myButton').remove(); // 即使按钮已被删除,关联着它的事件监听也不会自动消失 }, 5000); // 改进版——主动去除废弃不用的事件挂钩 const handlerId = Symbol('unique click listener'); document.getElementById('myButton').addEventListener('click', doSomething, {once: true}); setTimeout(() => { document.getElementById('myButton')?.removeEventListener('click', doSomething, {capture: false}); // 尝试安全地取消之前设置过的单次触发型监听者 }, 5000); ``` #### 合理规划全局变量的作用域与生命周期 尽量缩小公共接口暴露面仅限于确实必需的部分;采用模块化的开发思路将不同业务逻辑封装成独立单元并通过受控的方式共享信息流;定期审查项目源码寻找可能存在的冗余声明项进而加以精简整理。 ```javascript // 应避免的情况——随意向window上挂载成员 window.unnecessaryDataStructure = []; // 直接把数组丢到了最顶层环境中去... // 建议的方法——限定可见性影响范围 (function(globalScope){ 'use strict'; var privateStorage = []; globalScope.publicAPI = { addItem(item){privateStorage.push(item)}, clearItems(){privateStorage.length=0}, get length (){return privateStorage.length;} }; })(typeof module === 'object' && typeof exports !== undefined ? require('module') : self); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值