JS 垃圾回收机制

当前主流的垃圾回收机制是标记清除(mark-and-sweep)
标记清除基于可达性
如果一个值可以从开始通过引用或引用链被访问到,则认为该值是可达的。
下面这些都可以被看做是根(roots):

  • 当前执行的函数,它的局部变量和参数。
  • 当前嵌套调用链上的其他函数、它们的局部变量和参数。
  • 全局变量。

垃圾回收器定期执行以下“垃圾回收”步骤:

  • 垃圾收集器找到所有的根,并“标记”(记住)它们。
  • 然后它遍历并“标记”来自它们的所有引用。
  • 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
  • ……如此操作,直到所有可达的(从根部)引用都被访问到。
  • 没有被标记的对象都会被删除。

还有一种过时的垃圾回收机制是引用计数,如果引用数为 0 则被清除,但有缺陷,例如循环引用就不会被清除

  1. function foo() {
  2. var obj1 = {};
  3. var obj2 = {};
  4. obj1.x = obj2 ; // obj1引用obj2
  5. obj2.x = obj1 ; // obj2引用obj1
  6. return true ;
  7. }
  8. foo()

内存泄漏

内存泄漏是指非预期的垃圾无法清除

常见的场景
  1. 全局变量

    1. function foo(arg){
    2. bar =“some text”; // bar将泄漏到全局.
    3. }
  2. 被遗忘的定时器和回调函数

    1. var someResource = getData();
    2. setInterval(function() {
    3. var node = document.getElementById('Node');
    4. if(node) {
    5. node.innerHTML = JSON.stringify(someResource));
    6. // 定时器也没有清除
    7. }
    8. // node、someResource 存储了大量数据 无法回收
    9. }, 1000);
  3. DOM 引用 ```javascript var refA = document.getElementById(‘refA’); document.body.removeChild(refA); // dom删除了 console.log(refA, “refA”); // 但是还存在引用,能console出整个div 没有被回收

// 解决办法:refA = null;

  1. 4. 闭包错误使用
  2. ```javascript
  3. var theThing = null;
  4. var replaceThing = function () {
  5. var originalThing = theThing;
  6. var unused = function () {
  7. if (originalThing)
  8. console.log("hi");
  9. };
  10. theThing = {
  11. longStr: new Array(1000000).join('*'),
  12. someMethod: function () {
  13. console.log(someMessage);
  14. }
  15. };
  16. };
  17. setInterval(replaceThing, 1000);

解决办法

组件销毁时及时清除不必要的全局引用和回调函数,销毁定时器,解绑自定义事件,解绑DOM事件监听等等

WeakMap 和 WeakSet

WeakMap 的键只能是对象,是弱引用,可以被垃圾回收
WeakSet 只能是对象的集合,也可以被垃圾回收