ES6 中把引用分为强引用和弱引用,这个目前只有在 Set 和 Map 中才存在 。
强引用才会有引用计数叠加,只有引用计数为 0 的对象的内存才会被回收,所以一般需要手动回收内存(手动回收的前提在于标记清除法还没执行,还处于当前的执行环境) 。
而弱引用没有触发引用计数叠加,只要引用计数为 0,弱引用就会自动消失,无需手动回收内存 。
标记清除
当变量进入执行时标记为“进入环境”,当变量离开执行环境时则标记为“离开环境”,被标记为“进入环境”的变量是不能被回收的,因为它们正在被使用,而标记为“离开环境”的变量则可以被回收 。
环境可以理解为我们的执行上下文,全局作用域的变量只会在页面关闭时才会被销毁 。
// 假设这里是全局上下文 var b = 1; // b 标记进入环境 function func() { var a = 1; return a + b; // 函数执行时,a 被标记进入环境 } func(); // 函数执行结束,a 被标记离开环境,被回收 // 但是 b 没有标记离开环境
JavaScript 内存泄漏的一些场景
JavaScript 的内存回收机制虽然能回收绝大部分的垃圾内存,但是还是存在回收不了的情况 。程序员要让浏览器内存泄漏,浏览器也是管不了的 。
下面有些例子是在执行环境中,没离开当前执行环境,还没触发标记清除法 。所以你需要读懂上面 JavaScript 的内存回收机制,才能更好的理解下面的场景 。
意外的全局变量 // 在全局作用域下定义 function count(num) { a = 1; // a 相当于 window.a = 1; return a + num; }
不过在 eslint 帮助下,这种场景现在基本没人会犯了,eslint 会直接报错,了解下就好 。
遗忘的计时器
无用的计时器忘记清理,是最容易犯的错误之一 。
拿一个 vue 组件举个例子 。
上面的组件销毁的时候,setInterval 还是在运行的,里面涉及到的内存都是没法回收的(浏览器会认为这是必须的内存,不是垃圾内存),需要在组件销毁的时候清除计时器 。
遗忘的事件监听
无用的事件监听器忘记清理也是最容易犯的错误之一 。
还是使用 vue 组件举个例子 。
上面的组件销毁的时候,resize 事件还是在监听中,里面涉及到的内存都是没法回收的,需要在组件销毁的时候移除相关的事件 。
遗忘的 Set 结构
Set 是 ES6 中新增的数据结构,如果对 Set 不熟,可以看这里 。
如下是有内存泄漏的(成员是引用类型,即对象):
let testSet = new Set(); let value = https://www.isolves.com/it/cxkf/yy/js/2023-03-23/{ a: 1 }; testSet.add(value); value = null;
需要改成这样,才会没有内存泄漏:
let testSet = new Set(); let value = https://www.isolves.com/it/cxkf/yy/js/2023-03-23/{ a: 1 }; testSet.add(value); testSet.delete(value); value = null;
有个更便捷的方式,使用 WeakSet,WeakSet 的成员是弱引用,内存回收不会考虑这个引用是否存在 。
let testSet = new WeakSet(); let value = https://www.isolves.com/it/cxkf/yy/js/2023-03-23/{ a: 1 }; testSet.add(value); value = null;
遗忘的 Map 结构
Map 是 ES6 中新增的数据结构,如果对 Map 不熟,可以看这里 。
如下是有内存泄漏的(成员是引用类型,即对象):
let map = new Map(); let key = [1, 2, 3]; map.set(key, 1); key = null;
需要改成这样,才会没有内存泄漏:
let map = new Map(); let key = [1, 2, 3]; map.set(key, 1); map.delete(key); key = null;
有个更便捷的方式,使用 WeakMap,WeakMap 的键名是弱引用,内存回收不会考虑到这个引用是否存在 。
let map = new WeakMap(); let key = [1, 2, 3]; map.set(key, 1); key = null
遗忘的订阅发布
和上面事件监听器的道理是一样的 。
建设订阅发布事件有三个方法,emit、on、off 三个方法 。
还是继续使用 vue 组件举例子:
上面组件销毁的时候,自定义 test 事件还是在监听中,里面涉及到的内存都是没办法回收的,需要在组件销毁的时候移除相关的事件 。
遗忘的闭包
闭包是经常使用的,闭包能提供很多的便利,
首先看下下面的代码:
function closure() { const name = '名字'; return () => { return name.split('').reverse().join(''); } } const reverseName = closure(); reverseName(); // 这里调用了 reverseName
上面有没有内存泄漏?是没有的,因为 name 变量是要用到的(非垃圾),这也是从侧面反映了闭包的缺点,内存占用相对高,数量多了会影响性能 。
推荐阅读
- JavaScript中根据字符串中的范围规则,判断当前值是否符合条件
- 蜂蜜上火还是下火
- 汤姆·克鲁斯|外媒曝阿汤哥与女儿十年没见面,女儿每月收到275万却不了解父亲
- |身在职场为人处世,了解这六个技能,你也能做到圆滑做人
- 七个你需要知道的强大 JavaScript 优化技巧
- 从公司50亿参数AI绘画模型简单了解宅男AI小姐姐的生成模型
- 化妆|萌新学化妆不走弯路,这套攻略了解一下
- ep什么意思(ep英文缩写是什么意思)
- 交易|钱币交易市场上,你需要了解的规则
- 广东咸鸡的做法