浅谈JavaScript 中释放内存

765 阅读2分钟

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

JavaScript 中释放内存的方式

JavaScript 采用引用计数的方式来动态管理内存。

什么是引用计数呢?举例如下:

// 创建了一个 obj 对象,动态分配了一些内存 【obj 引用计数为 0】
let obj = { value: 'XXXXX' };

// data1 引用了 obj,【obj 引用计数变为 1】
let data1 = { value: obj }
// data2 引用了 obj,【obj 引用计数变为 2】
let data2 = { value: obj }

// data1 释放对 obj 的引用,【obj 引用计数变为 1】
data1.value = undefined;
// data2 释放对 obj 的引用,【obj 引用计数变为 0】
delete data2.value;

// 假设后续代码再无对 obj 的引用
// 此时 obj 引用计数为 0,当 GC 发生时,obj 的内存将会被回收

简单来说,JavaScript 引擎在运行时统计一块内存被引用的次数,并定期进行垃圾回收(GC)。如果一块堆内存的引用次数变为 0,则会在后续的 GC 中被回收,也就是我们常说的这块内存被释放了。

所以当一个 JavaScript 中的变量确定不会再被使用之后,我们可以手动解除对它的引用,以便能够释放内存。通常,解除引用的方式有几种:

  • 设为 undefined,例如 data.value = undefined
  • 设为 null,例如 data.value = null
  • delete,例如 delete data.value

它们的性能差异如何呢?

实验过程

delete

先来看看 delete 的性能表现。

正向 delete

(()=>{
  let totalTime = 0, N=10;
  for(let n=0; n<N; ++n){
    let a = {};
    for(let i=0; i<1000000; ++i){
     a[i] = i;
    }
    let startTime = Date.now();
    for(let i=0; i<1000000; ++i){
     delete a[i];
    }
    totalTime += Date.now() - startTime;
    a;
  }
  console.log(`avg ${totalTime/N}ms in ${N} times`);
})()

运行结果

avg 67.5ms in 10 times

反向 delete

(()=>{
  let totalTime = 0, N=10;
  for(let n=0; n<N; ++n){
    let a = {};
    for(let i=0; i<1000000; ++i){
     a[i] = i;
    }
    let startTime = Date.now();
    for(let i=1000000-1; i>-1; --i){
     delete a[i];
    }
    totalTime += Date.now() - startTime;
    a;
  }
  console.log(`avg ${totalTime/N}ms in ${N} times`);
})()

结果

avg 64.4ms in 10 times

设为 undefined

(()=>{
  let totalTime = 0, N=10;
  for(let n=0; n<N; ++n){
    let a = {};
    for(let i=0; i<1000000; ++i){
     a[i] = i;
    }
    let startTime = Date.now();
    for(let i=0; i<1000000; ++i){
     a[i] = undefined;
    }
    totalTime += Date.now() - startTime;
    a;
  }
  console.log(`avg ${totalTime/N}ms in ${N} times`);
})()

运行结果

avg 0.8ms in 10 times

设为 null

(()=>{
  let totalTime = 0, N=10;
  for(let n=0; n<N; ++n){
    let a = {};
    for(let i=0; i<1000000; ++i){
     a[i] = i;
    }
    let startTime = Date.now();
    for(let i=0; i<1000000; ++i){
     a[i] = null;
    }
    totalTime += Date.now() - startTime;
    a;
  }
  console.log(`avg ${totalTime/N}ms in ${N} times`);
})()

运行结果

avg 0.8ms in 10 times

结论

  • 通过设为 undefined 或 null 来解引用,比 delete 快近 70 倍
  • 没有看出明显的内存差异。

所以,在性能敏感的场景解引用,通过赋值为 undefined 或 null 性能更佳。