【问题标题】:In JS, which is faster: Object's "in" operator or Array's indexof?在 JS 中,哪个更快:对象的“in”运算符还是数组的 indexof?
【发布时间】:2011-10-12 09:22:22
【问题描述】:

我想保留一个我只会检查是否存在的字符串列表,例如:

corporatePlan = [
    'canDeAuthorize',
    'hasGmailSupport',
    'canShareReports',
    'canSummonKraken',
    'etc'
]

所以,当用户尝试召唤海妖时,我会做corporatePlan.indexof('canSummonKraken') != -1 看看他是否可以。

一位同事建议将其存储为对象会更快:

"Corporate Plan" = {
    'canDeAuthorize' : null,
    'hasGmailSupport' : null,
    'canShareReports' : null,
    'canSummonKraken' : null,
    'etc' : null
}

只需执行'canSummonKraken' in corporatePlan 之类的操作即可检查计划是否包含该密钥。这在经典的 CS 意义上是有意义的,因为“包含”当然是地图上的常数时间和数组上的线性时间。不过,这是否检查了数组和对象在 JS 中是如何实现的?

在我们的特殊情况下,元素少于 100 个,速度并不重要。但是对于更大的数组,哪种方式访问​​速度会更快?

【问题讨论】:

  • 不要忘记对象文字中nulls 后面的逗号。
  • @Fish:FWIW,我更新了我的答案,提供了第三种选择,我认为这是一种相当正常的方式。
  • 检查这个基准:jsben.ch/#/Y9jDP

标签: javascript performance


【解决方案1】:

在 JavaScript 中,您通常要处理各种各样的实现(除非您在受控环境中使用它,例如您选择引擎的服务器),因此特定性能问题的答案往往是“这取决于,检查你将要使用的引擎。”一种实现速度最快的实现可能在另一种实现上更慢,等等。http://jsperf.com 对这类事情很方便。

也就是说,我希望in 在这里成为明显的赢家。 Array#indexOf 必须在搜索中访问数组索引,并且数组索引是属性,就像任何其他属性一样。因此访问数组索引0 以查看它是否是所需的字符串需要查找0,就像另一个需要查找属性"canSummonKraken"(然后它必须在之后进行字符串比较)。 (是的,数组索引是属性。JavaScript 中的数组aren't really arrays 根本就没有。)indexOf 在搜索过程中可能需要访问多个属性,而in 只需要访问一个。但同样,您需要在目标环境中检查它以确保某些实现可能会优化具有连续索引范围的数组(但最慢的肯定不会,当然如果您担心速度,您'担心在最慢的引擎上什么是最快的,比如 IE)。

还请注意,并非所有 JavaScript 引擎都具有Array#indexOf。大多数都可以,但仍然有一些较旧的(我在看着你,微软)没有。

您还有使用in 还是hasOwnProperty 的问题。使用in 的好处是它是一个操作符,而不是一个函数调用;使用hasOwnProperty 的优点是它只会查看特定的对象实例而不是其原型(及其原型等)。除非你有一个非常深的继承层次结构(而你在你的例子中没有),我敢打赌in 会获胜,但记住它确实会检查层次结构是很有用的。

另外,请记住 "canSummonKraken" in obj 在您展示的示例对象字面量中为真,因为该对象确实具有该属性,即使该属性的值为空。您必须完全没有属性 才能返回 false。 (而不是in,您可以只使用true 和false 并将其查找为obj.canSummonKraken。)

所以你的选择是:

  1. 你的数组方法:

    corporatePlan = [
        'canDeAuthorize',
        'hasGmailSupport',
        'canShareReports',
        'canSummonKraken',
        'etc'
    ];
    
    console.log(corporatePlan.indexOf("canSummonKraken") >= 0);  // true
    console.log(corporatePlan.indexOf("canDismissKraken") >= 0); // false
    

    ...我不推荐。

  2. in 方法:

    corporatePlan = {
        'canDeAuthorize'  : null,
        'hasGmailSupport' : null,
        'canShareReports' : null,
        'canSummonKraken' : null,
        'etc'             : null
    };
    
    console.log("canSummonKraken" in corporatePlan);  // true
    console.log("canDismissKraken" in corporatePlan); // false
    

    可能比indexOf 快,但我会测试一下。如果列表可能很长,并且如果您将拥有大量此类对象,则很有用,因为它只需要根本存在“真实”属性。一个空对象代表一个用户不能做任何事情的计划,并且非常小。

    这里我要注意两点:

    1. in 也会检查对象的原型,所以如果你有 toStringvalueOf 这样的设置,你会得到误报(因为这些属性几乎所有对象都来自 @987654346 @)。在支持 ES5 的浏览器上,您可以通过使用 null 原型创建对象来避免该问题:var corporatePlan = Object.create(null);

    2. 可能是因为它检查原型,in 运算符在某些引擎上出奇地慢

    这两个问题都可以通过使用hasOwnProperty 来解决:

    console.log(corporatePlan.hasOwnProperty("canSummonKraken"));  // true
    console.log(corporatePlan.hasOwnProperty("canDismissKraken")); // false
    

    人们会认为操作符比方法调用更快,但事实证明这并不是真正的跨浏览器。

  3. 标志方法:

    corporatePlan = {
        'canDeAuthorize'   : true,
        'hasGmailSupport'  : true,
        'canShareReports'  : true,
        'canSummonKraken'  : true,
        'canDismissKraken' : false,
        'etc'              : true
    };
    
    console.log(corporatePlan.canSummonKraken);  // "true"
    console.log(corporatePlan.canDismissKraken); // "false"
    
    // or using bracketed notation, in case you need to test this
    // dynamically
    console.log(corporatePlan["canSummonKraken"]);  // "true"
    console.log(corporatePlan["canDismissKraken"]); // "false"
    
    // example dynamic check:
    var item;
    item = "canSummonKraken";
    console.log(corporatePlan[item]);  // "true"
    item = "canDismissKraken";
    console.log(corporatePlan[item]);  // "false"
    

    ...这将是一种相当正常的方式,可能比in 更快,并且可能至少与hasOwnProperty 一样快。 (但请参阅我的开头段落:在您的环境中测试。:-))

【讨论】:

  • +1,我同意,还没有测试过,但是in 不需要像索引那样查找项目。
【解决方案2】:

我已经测试过了:http://jsperf.com/array-indexof-vs-object-s-in-operator/4

当找到第一个元素时,两者都有很好的结果,具体取决于正在使用的浏览器。 所以找到最后一个元素,in 运算符要快得多。

但后来我使用了带有 typeof 运算符的变体,它比两者都快得多:

if (typeof obj['that'] !== "undefined") {
  // found
}

【讨论】:

  • 为什么是typeof !== 'undefined',而不是!== undefined
  • typeof 返回一个字符串而不是一个对象;你在那里增加了一个额外的强制。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • @Nevir 他的意思是obj['that'] !== undefined,这比字符串比较快。
  • undefined 不是常量,可以被覆盖。检查"undefined" 更加安全。
  • @oncode 这在现代浏览器中不再适用;更重要的是,这从一开始就完全是偏执狂。 typeof 代码看起来更复杂。
【解决方案3】:

这是一个基准http://jsperf.com/array-indexof-vs-object-keys。 在 chrome 和 firefox 中,检查对象中是否存在键比扫描数组快 100%。

但是如果你把初始化时间考虑在内,差异就会抵消,对象比数组需要更多的时间来初始化。

【讨论】:

  • 要正确测试,您应该“删除”初始化时间:在这两种情况下都会初始化两个实体——对象和数组。然后在第一种情况下仅测试数组和在第二种情况下 -- 对象。
【解决方案4】:

对象引用是明显的赢家

我运行了一个更简单的测试,它只真正测试:array.indexOf 操作并与object[key] 引用和值here 进行比较。

这表明当仅涉及 1 个键且查找失败时,object[key] 运算符的速度要快 ~6X

这实际上是对最佳情况数据集(1 个条目)的最坏情况查找(失败),应该作为大多数此类操作的良好代理。

随着每个键添加到源Array a/Object o,速度差距越来越大。

Object 参考是明显的赢家。

【讨论】:

    【解决方案5】:

    我认为 Boolean(corporatePlan[item]) 很明确

    const t0 = performance.now();
    const a = [], b=[];
    function getRandomInt(max) {
      return Math.floor(Math.random() * Math.floor(max));
    }
    for(let i=0; i<1000; i++) a.push((i+1)+"");
    for(let i=0; i<100; i++) b.push(getRandomInt(10000)+"");
    const bmap = a.reduce((map, x)=>{map[x]=1; return map;}, {});
    const c = b.filter((x)=>x in bmap);
    const t1 = performance.now();
    console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");
    
    // Call to doSomething took 0.360000180080533 milliseconds.
    // ---------------------------------------------------------------------------------
    
    const t0 = performance.now();
    const a = [], b=[];
    function getRandomInt(max) {
      return Math.floor(Math.random() * Math.floor(max));
    }
    for(let i=0; i<1000; i++) a.push((i+1)+"");
    for(let i=0; i<100; i++) b.push(getRandomInt(10000)+"");
    const c = b.filter((x)=>a.indexOf(x) !== -1);
    const t1 = performance.now();
    console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");
    
    // Call to doSomething took 0.5349998828023672 milliseconds.
    // ---------------------------------------------------------------------------------
    
    const t0 = performance.now();
    const a = [], b=[];
    function getRandomInt(max) {
      return Math.floor(Math.random() * Math.floor(max));
    }
    for(let i=0; i<1000; i++) a.push((i+1)+"");
    for(let i=0; i<100; i++) b.push(getRandomInt(10000)+"");
    const bmap = a.reduce((map, x)=>{map[x]=1; return map;}, {});
    const c = b.filter((x)=>typeof(bmap[x]) !== "undefined");
    const t1 = performance.now();
    console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");
    
    // Call to doSomething took 0.3500001039355993 milliseconds.
    // ---------------------------------------------------------------------------------
    
    
    
    const t0 = performance.now();
    const a = [], b=[];
    function getRandomInt(max) {
      return Math.floor(Math.random() * Math.floor(max));
    }
    for(let i=0; i<1000; i++) a.push((i+1)+"");
    for(let i=0; i<100; i++) b.push(getRandomInt(10000)+"");
    const bmap = a.reduce((map, x)=>{map[x]=1; return map;}, {});
    const c = b.filter((x)=>Boolean(bmap[x]));
    const t1 = performance.now();
    console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");
    
    // Call to doSomething took 0.260000117123127 milliseconds.
    // ---------------------------------------------------------------------------------
    
    const t0 = performance.now();
    const a = [], b=[];
    function getRandomInt(max) {
      return Math.floor(Math.random() * Math.floor(max));
    }
    for(let i=0; i<1000; i++) a.push((i+1)+"");
    for(let i=0; i<100; i++) b.push(getRandomInt(10000)+"");
    const bmap = a.reduce((map, x)=>{map[x]=1; return map;}, {});
    const c = b.filter((x)=>bmap[x]);
    const t1 = performance.now();
    console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");
    
    // Call to doSomething took 0.3450000658631325 milliseconds.
    // ---------------------------------------------------------------------------------
    

    【讨论】:

      猜你喜欢
      • 2019-11-13
      • 1970-01-01
      • 2011-04-01
      • 1970-01-01
      • 2012-03-17
      • 2012-02-29
      • 1970-01-01
      • 1970-01-01
      • 2023-03-05
      相关资源
      最近更新 更多