垃圾回收机制
js内置的内存管理机制。js会定期寻找没有被用到的属性,然后释放它们所占用的空间。
如果没有垃圾回收机制,我们在开发中创建的变量会占用大量内存,没有手动清除的话,内存会被占满,导致程序崩溃。
垃圾回收机制的主要策略为“标记-清除算法”:
标记:js会从全局对象/当前函数中的变量(统称为“根”)开始一级一级的往下查找,如果下一个对象能以这种唯一路径从根访问到,那么这个对象就称为“可达”,垃圾回收机制不会回收这个对象;如果不能从根访问到,那么这个对象就称为“不可达”,会被垃圾回收机制回收掉。
清除:把“不可达”状态的对象清除,释放出来空间。
// 全局对象
let globalObj = {name:"张三",age:18};
console.log(myObj.name)
// 能从全局(window)对象找到myObj,所以myObj为“可达”,不会被清除
// 局部对象
const fn = () => {
const localObj = {name:"李四",age:19};
console.log(localObj.name)
}
fn();
// fn执行后 localObj会被清除,{name:"李四",age:19}这个对象的唯一访问路径就断了,所以为“不可达”,会被垃圾回收机制清除
内存泄漏
因为某些错误操作导致某些应该被回收的对象无法被垃圾回收机制清除,造成内存的无效占用,最后使程序卡顿或崩溃。常见的错误操作:
1、在非严格模式下,某个函数内部给一个不存在的变量赋值,则会自动创建一个全局变量,函数执行完成后,该变量不会被回收。(解决:使用严格模式,或者不要犯给一个不存在的变量赋值这种低级错误)
2、setTimeout或setInterval中操作了某个dom,即使该dom被手动移除,只要定时器/延时器还在,该dom就不会被回收(解决:及时clearTimeout/clearInterval清除定时器/延时器实现垃圾回收)。
3、创建一个变量获取某个dom,即使该dom被手动移除,只要变量还在,该dom就不会被回收(解决:移除dom时,让该变量为null)。
4、闭包中会嵌套调用变量,保持变量的持久性,如果闭包嵌套次数过多,会因内部变量无法被回收导致内存泄漏(解决:慎用闭包,及时清空没用的变量)。
5、给某个dom添加事件监听器,移除dom时,个别浏览器不会自动移除事件监听器,该监听器无法被回收,造成内存泄漏(解决:及时移除事件监听器)。
6、给Object或者Map塞大量数据,因为Object和Map的键值都是强引用,只要这个Object/Map被其他变量使用,即使清空该Object/Map,内存依然不会被释放(解决:对家大量数据时,使用WeakMap/WeakSet,其键值是弱引用)
强/弱引用
强引用:我们平时使用的都是强引用,只要有A变量使用B变量,及时B变量被清空,因为A的强引用,B变量的对象依然存在。
let obj = {name:"张三"};
const a = obj;
obj = null;
// 因为a的值依然是{name:"张三"},所以{name:"张三"}这个对象不会被回收
弱引用:在js中,弱引用一般都是用过WeakMap/WeakSet实现,一个对象如果被弱引用所引用,垃圾回收机制就会想对待正常对象一样回收它,回收后,弱引用的值为undefined。
const map = new WeakMap();
const obj = {name:"张三"};
map.set(obj,'我是弱引用的value');
obj = null;
// 因为map是弱引用obj,所以当清空obj时,{name:"张三"}这个对象就会被回收,此时无法通过map.get(obj)获取该值了