【问题标题】:for-in vs Object.keys forEach without inherited propertiesfor-in vs Object.keys forEach 没有继承属性
【发布时间】:2014-09-23 00:51:14
【问题描述】:

我正在查看Object.keys + forEachfor-in 与普通对象的性能基准。

This 基准测试表明Object.keys + forEachfor-in 方法慢62%。但是,如果您不想获得继承的属性,该怎么办? for-in 包含所有非原生继承对象,所以我们必须使用 hasOwnProperty 来检查。

我试图让另一个benchmark here 做到这一点。但现在for-in 方法比Object.keys + forEach41%


更新

以上测试是在 Chrome 中完成的。再次测试,但使用 Safari,我得到了不同的结果:Object.keys(..).forEach(..) 34% slower,奇怪。

注意:我进行基准测试的原因是检查 Node.js 的情况。

问题:

  • Chromejsperf 结果对于 Node.js 是否相当可观?
  • 发生了什么事,为什么单个条件使 for-in 方法比 Chrome 中的 Object.keys + forEach41%

【问题讨论】:

  • 您能在这里突出显示问题吗?似乎有很多要回答的。
  • 更新了我的问题。很抱歉造成混乱。

标签: javascript inheritance


【解决方案1】:

node.js 使用 V8,虽然我猜它与 Chrome 中的当前版本不一样,但我想它是 node 在该主题上表现的一个很好的指标。

其次,您正在使用forEach,这在开发时非常方便,但会为每次迭代添加一个回调,这是一项(相对)冗长的任务。所以,如果你对表演感兴趣,为什么不直接使用普通的for 循环呢?

for (var i = 0, keys = Object.keys(object); i < keys.length; i++) {
    // ...
}

这会产生您可以获得的最佳性能,也可以解决您在 Safari 中的速度问题。

简而言之:这不是条件,而是对hasOwnProperty 的调用产生了影响。您在每次迭代时都在进行函数调用,这就是 for...in 变慢的原因。

【讨论】:

  • 是的,但我做了第二次测试。表明 forEach 要快得多。考虑 for-in 不应包含继承的属性。
  • 将此 sn-p 添加到基准测试中。它在 2 中排名第一。谢谢。
  • 顺便说一句,在循环的条件部分调用 Object.keys() 并不是一个好主意,因为它将在每次迭代中调用。
  • @AskarovBeknar 我不知道你是否指的是我上面的 sn-p,但由于那个确切的原因,这不是发生的事情。
  • 啊。是的对不起。它实际上是在初始化部分。那么它是完全正确的。你是对的,使用 for 循环而不是 foreach 会加快执行速度,因为它不会在每次迭代时都创建一个闭包,你可以从中中断。
【解决方案2】:

请注意:

var keys = Object.keys(obj), i = keys.length;

while(--i) {
   //
}

不会为索引 0 运行,然后你会错过你的一个属性。

这在像 ["a","b","c","d"] 这样的数组中只会运行 d,c,b,你会错过 "a" 因为索引是 0 和 0是假的。

需要在while检查后递减:

var keys = Object.keys(obj), i = keys.length;

while(i--) {
   //
}

【讨论】:

    【解决方案3】:

    我今天也对此感兴趣,但主要是因为我不喜欢在我已经知道我的对象是干净的(因为它们是从对象文字创建的)时必须使用 hasOwnProperty 测试来传递默认 lint。无论如何,我对@styonsk 的答案进行了一些扩展,以包含更好的输出并运行多个测试并返回输出。

    结论:节点复杂。最好的时间看起来像是在 nodejs v4.6.1 上使用带有数字 for 循环或 while 循环的 Object.keys()。在 v4.6.1 上,带有 hasOwnProperty 的 forIn 循环是最慢的方法。但是,在节点 v6.9.1 上它是最快的,但它仍然比 v4.6.1 上的两个 Object.keys() 迭代器慢。

    注意:这是在 2013 年末配备 16GB 内存和 2.4Ghz i5 处理器的 MacBook Pro 上运行的。在测试期间,每个测试都与单个 cpu 的 100% 挂钩,平均 rss 约为 500MB,峰值为 1GB 的 rss。希望这对某人有所帮助。

    这是我针对具有大对象(10^6 个属性)和小对象(50 个属性)的 nodejs v6.9.1 和 v4.6.1 运行的结果

    具有大对象 10^6 属性的节点 v4.6.1

    testObjKeyWhileDecrement 测试次数: 100 总时间: 57595 毫秒 平均时间: 575.95 毫秒

    testObjKeyForLoop 测试次数: 100 总时间: 54885 毫秒 平均时间: 548.85 毫秒

    testForInLoop 测试次数: 100 总时间: 86448 毫秒 平均时间: 864.48 毫秒

    具有小对象 50 个属性的节点 v4.6.1

    testObjKeyWhileDecrement 测试计数: 1000 总时间: 4 毫秒 平均时间: 0.004 毫秒

    testObjKeyForLoop 测试计数: 1000 总时间: 4 毫秒 平均时间: 0.004 毫秒

    testForInLoop 测试计数: 1000 总时间: 14 毫秒 平均时间: 0.014 毫秒

    具有大对象 10^6 属性的节点 v6.9.1

    testObjKeyWhileDecrement 测试次数: 100 总时间: 94252 毫秒 平均时间: 942.52 毫秒

    testObjKeyForLoop 测试次数: 100 总时间: 92342 毫秒 平均时间: 923.42 毫秒

    testForInLoop 测试次数: 100 总时间: 72981 毫秒 平均时间: 729.81 毫秒

    具有小对象 50 个属性的节点 v4.6.1

    testObjKeyWhileDecrement 测试计数: 1000 总时间: 8 毫秒 平均时间: 0.008 毫秒

    testObjKeyForLoop 测试计数: 1000 总时间: 10 毫秒 平均时间: 0.01 毫秒

    testForInLoop 测试计数: 1000 总时间: 13 毫秒 平均时间: 0.013 毫秒

    以下是我运行的代码:

    //Helper functions
    function work(value) {
      //do some work on this value
    }
    
    function createTestObj(count) {
      var obj = {}
      while (count--) {
        obj["key" + count] = "test";
      }
      return obj;
    }
    
    function runOnce(func, obj) {
      var start = Date.now();
      func(obj);
      return Date.now() - start;
    }
    
    function testTimer(name, func, obj, count) {
      count = count || 100;
      var times = [];
      var i = count;
      var total;
      var avg;
    
      while (i--) {
        times.push(runOnce(func, obj));
      }
    
      total = times.reduce(function (a, b) { return a + b });
      avg = total / count;
    
      console.log(name);
      console.log('Test Count: ' + count);
      console.log('Total Time: ' + total);
      console.log('Average Time: ' + avg);
      console.log('');
    }
    
    //Tests
    function testObjKeyWhileDecrement(obj) {
      var keys = Object.keys(obj);
      var i = keys.length;
      while (i--) {
        work(obj[keys[i]]);
      }
    }
    
    function testObjKeyForLoop(obj) {
      var keys = Object.keys(obj);
      var len = keys.length;
      var i;
      for (i = 0; i < len; i++) {
        work(obj[keys[i]]);
      }
    }
    
    function testForInLoop(obj) {
      for (key in obj) {
        if (obj.hasOwnProperty(key)) {
          work(obj[key]);
        }
      }
    }
    
    //Run the Tests
    var data = createTestObj(50)
    testTimer('testObjKeyWhileDecrement', testObjKeyWhileDecrement, data, 1000);
    testTimer('testObjKeyForLoop', testObjKeyForLoop, data, 1000);
    testTimer('testForInLoop', testForInLoop, data, 1000);
    

    【讨论】:

    • 我刚刚测试了 Object.keys(obj).forEach(work),它正好适合 forIn 和 Object.keys 之间的数字 for 和 while 循环。
    • 我在这里整理了一个 jsperf:jsperf.com/object-property-iteration
    【解决方案4】:

    对于那些仍然关心在 JS 中迭代对象属性的人来说,绝对最快的方法是:

    var keys = Object.keys(obj), i = keys.length;
    
    while(--i) {
       //
    }
    

    http://jsperf.com/object-keys-foreach-vs-for-in-hasownproperty/8

    您可以通过不必重新计算键数组的长度值(在现代浏览器优化中几乎可以忽略不计)来节省一些大型对象,这对于简单的 for 循环也是如此。递减的 while 循环仍然比 for 循环或递增的 while 循环与长度上限比较快很多。

    【讨论】:

    【解决方案5】:

    我今天测试了这个。就我的目的而言,获取 Object 键然后执行普通的旧 for 循环比执行递减的 while 或 for in 循环要快。随意更改此模板以针对您的个人情况测试不同的循环:

    //Helper functions
    function work(value) {
      //do some work on this value
    }
    
    function createTestObj(count) {
      var obj = {}
      while (count--) {
        obj["key" + count] = "test";
      }
      return obj;
    }
    
    //Tests
    function test_ObjKeyWhileDecrement(obj) {
      console.log("Time Started: ", new Date().getTime());
      var keys = Object.keys(obj),
        i = keys.length;
      while (i--) {
        work(obj[keys[i]]);
      }
      console.log("Time Finished: ", new Date().getTime());
    }
    
    function test_ObjKeyForLoop(obj) {
      console.log("Time Started: ", new Date().getTime());
      for (var i = 0, keys = Object.keys(obj); i < keys.length; i++) {
        work(obj[keys[i]]);
      }
      console.log("Time Finished: ", new Date().getTime());
    }
    
    function test_ForInLoop(obj) {
      console.log("Time Started: ", new Date().getTime());
      for (key in obj) {
        work(obj[key]);
      }
      console.log("Time Finished: ", new Date().getTime());
    }
    
    //Run the Tests
    var data = createTestObj(1000 * 100)
    console.log("Test Obj Key While Decrement Loop")
    test_ObjKeyWhileDecrement(data);
    console.log("Test Obj Key For Loop")
    test_ObjKeyForLoop(data);
    console.log("Test For In Loop")
    test_ForInLoop(data);

    您可能希望在实际环境中运行它来测试,而不是在 jsfiddle 中。也尝试多个浏览器。

    【讨论】:

    • 你应该使用console.timeconsole.timeEnd来处理这种情况。
    【解决方案6】:

    对于 ES6 粉丝来说,看起来像

    Object.keys(obj).reduce((a,k) => {a += obj[k]; return a}, res)
    

    是迄今为止最快的。

    https://jsperf.com/for-in-vs-for-of-keys-vs-keys-reduce

    【讨论】:

    • 2018:keys-reduce 在 V8 上仅快 0.5%,但在 FireFox 上慢 2%。
    猜你喜欢
    • 2017-02-09
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    • 1970-01-01
    • 2022-12-18
    • 2015-12-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多