1. 浏览器怎么进行垃圾回收?
回答这个问题可以从三个点来回答,什么是垃圾?如何捡垃圾?什么时候捡垃圾?
1.什么是垃圾?
- 不再需要的数据,即为垃圾
- 全局变量随时可能用到,不是垃圾
举例:
1
2
3
let dog = new Object();
let dog.a = new Array(1);
dog.a = new Object()
当 JavaScript 执行这段代码的时候,会先在全局作用域中添加一个dog
属性,并在堆中创建了一个空对象,将该对象的地址指向了 dog
。
随后又创建一个大小为 1 的数组,并将属性地址指向了 dog.a
。
将另外一个对象赋给了 a
属性。
此时,数组对象就成了不被使用的数据,即 不可达数据。就是需要被回收的垃圾数据。
2.如何捡垃圾?
- 标记空间中的 可达值。从根节点出发,遍历所有对象,可以遍历到的对象是可达的,没有被遍历到的即为不可达数据。
- 回收 不可达的值所占的内存
-
做内存整理
在浏览器环境中,根节点有很多。比如:全局变量window 、文档DOM 树、存放在栈上的变量
3.什么时候捡垃圾?
前端有其特殊性,垃圾回收的时候会造成页面卡顿。所以垃圾回收有以下几种收集方式:分代收集、增量收集、闲时收集。
分代收集
一句话总结分代回收就是:将堆分为新生代与老生代,多回收新生代,少回收老生代。
浏览器将数据分为临时对象和长久对象。
临时对象:
- 大部分对象在内存中存活时间很短
- 函数内部声明的变量、块级作用域中的变量。当函数或代码块执行结束后,其中定义的变量就被销毁
- 这类对象很快就变得不可访问,应快点回收
长久对象:
- 生命周期很长的对象。比如全局的window、DOM、web API等
- 这类对象应慢点回收
V8把堆分为新生代和老生代两个区域。新生代存放临时对象,老生代存放长久对象。并且让副垃圾回收器、主垃圾回收器分别负责新生代和老生代。
主垃圾回收器的特点:
- 对象占用空间大
- 对象存活时间长
- 使用标记-清除算法进行垃圾回收
副垃圾回收器特点:
- 负责新生代的垃圾回收,通常只支持1-8M的容量
- 分为对象区域、空闲区域
增量收集
脚本中有许多对象,引擎一次性遍历整个对象,会造成长时间暂停。所以引擎将垃圾收集分成更小的块,每次处理一部分,多次处理。解决长时间停顿的问题。
闲时收集
垃圾收集器只会在cpu空闲时尝试运行,减少可能对代码执行的影响。
2.浏览器中不同类型变量的内存何时释放?
javascript中类型:值类型、引用类型。
引用类型: 在没有引用之后,通过V8自动回收
值类型:如果处于闭包中,则等闭包没有引用才会被回收;如果非闭包情况下,等V8的新生代切换时候回收。
3.哪些情况会导致内存泄露?如何避免?
内存泄露:指用不到的数据依然占据着内存空间,不能被再次利用。
以Vue为例,通常有这些情况:
- 监听在window/body等事件没有解绑
- 绑在EventBus的事件没有解绑
- Vuex的$store,watch之后没有unwatch
- 使用第三方库创建,没有调用正确的销毁函数
解决办法:beforeDestroy中及时销毁
- 绑定了ad dEventListener事件,要removeEventListener
- 观察者模式$on 和 $off处理
- 如果组件中使用了定时器,应销毁处理
- 如果在mounted/created钩子中使用了第三方库初始化,对应销毁
- 使用弱引用weakMap 、weakSet
4.闭包会导致内存泄漏吗?
不会。内存泄露是指你「用不到」(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。闭包里面的变量就是我们需要的变量,不能说是内存泄露。