Array.forEach()和closure

时间:2017-03-10 22:30:57

标签: javascript performance

在我阅读an article about JavaScript optimization之后,我意识到需要删除代码中的闭包以优化内存使用。

我的一个代码模式是尽可能使用Array.forEach(),即使在这种情况下:

  • 使用数组中的项修改外部对象

    function updateSomething(array, toChange) {
      array.forEach(item => {
        toChange[item] = ''; // do something to change the object
      });
    }
    
  • 创建嵌套的Array.forEach()

    array1.forEach(item1 => {
      array2.forEach(item2 => {
        doSomething(item1, item2);
      });
    });
    

显然Array.forEach()中使用的回调函数会创建闭包。在这些情况下,我是否滥用Array.forEach()?我应该在性能敏感的项目中回到for循环吗?

跟进

我已经使用Node v7.6.0对for循环和Array.forEach()函数进行了一些实验。我没有先前的性能测试经验。如果我做错了,请告诉我。

  • Array.forEach()访问外部变量

    // get the baseline of memory usage
    gc();
    
    let baseline = process.memoryUsage();
    
    console.log(`Baseline memory usage: ${baseline.heapUsed / 1024 } KB`);
    
    let data = { test: 0 };
    
    function test(data) {
      let array1 = new Array(1000000).fill(1);
      array1.forEach((item) => {
        data.test = data.test + item;
      });
    }
    
    test(data);
    
    let final = process.memoryUsage();
    
    console.log(`Final memory usage: ${final.heapUsed / 1024} KB`);
    console.log(`Memory used: ${(final.heapUsed - baseline.heapUsed) / 1024} KB`);
    

    结果

    Baseline memory usage: 2747.671875 KB
    Final memory usage: 11027.34375 KB
    Memory used: 8279.671875 KB
    
  • for循环

    // get the baseline of memory usage
    gc();
    
    let baseline = process.memoryUsage();
    
    console.log(`Baseline memory usage: ${baseline.heapUsed / 1024 } KB`);
    
    let data = { test: 0 };
    
    function test(data) {
      let array1 = new Array(1000000).fill(1);
      for(let i = 0; i < 1000000; i++) {
        data.test = data.test + array1[i];
      }
    }
    
    test(data);
    
    let final = process.memoryUsage();
    
    console.log(`Final memory usage: ${final.heapUsed / 1024} KB`);
    console.log(`Memory used: ${(final.heapUsed - baseline.heapUsed) / 1024} KB`);
    

    结果

    Baseline memory usage: 2747.453125 KB
    Final memory usage: 11031.546875 KB
    Memory used: 8284.09375 KB
    
  • 嵌套Array.forEach()

    // get the baseline of memory usage
    gc();
    
    let baseline = process.memoryUsage();
    
    console.log(`Baseline memory usage: ${baseline.heapUsed / 1024 } KB`);
    
    let array1 = new Array(1000).fill(1);
    let array2 = new Array(1000).fill(2);
    
    array1.forEach((item, index) => {
      array2.forEach(item2 => {
        array1[index] = array1[index] = item2;
      })
    });
    
    let final = process.memoryUsage();
    
    console.log(`Final memory usage: ${final.heapUsed / 1024} KB`);
    console.log(`Memory used: ${(final.heapUsed - baseline.heapUsed) / 1024} KB`);
    

    结果

    Baseline memory usage: 2748.109375 KB
    Final memory usage: 3368.5859375 KB
    Memory used: 620.4765625 KB
    
  • 嵌套for循环

    // get the baseline of memory usage
    gc();
    
    let baseline = process.memoryUsage();
    
    console.log(`Baseline memory usage: ${baseline.heapUsed / 1024 } KB`);
    
    let array1 = new Array(1000).fill(1);
    let array2 = new Array(1000).fill(2);
    
    for (let i = 0; i < 1000; i++) {
      for (let j = 0; j < 1000; j++) {
        array1[i] = array1[i] + array2[j];
      }
    }
    
    let final = process.memoryUsage();
    
    console.log(`Final memory usage: ${final.heapUsed / 1024} KB`);
    console.log(`Memory used: ${(final.heapUsed - baseline.heapUsed) / 1024} KB`);
    

    结果

    Baseline memory usage: 2745.59375 KB
    Final memory usage: 3234.2890625 KB
    Memory used: 488.6953125 KB
    

结论

测试#1和#2的内存使用量增加的差异小于0.1%。所以它表明Array.forEach()确实具有与传统for循环相同的内存效率,即使它正在访问外部变量并且似乎创建闭包。魔术是在内部完成的。

注意到在测试#3中,array2.forEach()的回调函数初始化了1000次。这可以解释为什么测试#3比测试#4使用更多的内存。

1 个答案:

答案 0 :(得分:0)

  

我意识到需要删除代码中的闭包以优化内存使用。

对于内存使用,只有您存储某处的闭包才算数。当你遇到内存问题时,你应该检查是否有包含大量实例的类,每个实例都有自己的闭包实例。这并不意味着你应该避免一般的闭包。

  

我的一个代码模式是尽可能多地使用Array.forEach()

别。鉴于您使用的是ES6,您应该尽可能多地使用for … of(对于命令式循环)。

  

显然Array.forEach()中使用的回调函数会创建闭包

是的,但是在你展示的例子中,它们是不可避免的(不能移动到静态函数中)。并且鉴于它们仅在forEach调用时持续并且将在之后立即进行垃圾收集,因此也没有内存压力。

但是,正如您链接的文章所解释的那样,创建封装的成本仍然很高(与根本不创建封装相比)。

  

我应该在性能敏感的项目中回到for循环吗?

是的,肯定(至少在真正性能敏感的位置 - 没有完整的项目)。然而,原因不是闭包的成本,而是forEach无法完全优化的函数的调用开销。