在JS中,哪个更快:Object的“in”运算符或Array的indexof?

时间:2011-10-12 09:22:23

标签: javascript performance

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

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个元素,速度并不重要。但是对于更大的阵列,访问哪种方式会更快?

5 个答案:

答案 0 :(得分:26)

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

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

另请注意,并非所有JavaScript引擎都有Array#indexOf。大多数人都这样做,但仍然有一些较老的人(我正在看着你,微软)不这样做。

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

另外,请记住,"canSummonKraken" in obj在您显示的示例对象文字中将为true,因为该对象确实具有该属性,即使该属性的值为null。您必须没有所有属性才能返回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快,但我会测试它。如果列表可能很长并且你将要拥有很多这些对象,那么它很有用,因为它只需要存在“truthy”属性。空对象表示用户无法执行任何操作的计划,并且非常小。

    我应该注意两件事:

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

    2. 也许是因为它检查了原型,in运算符在某些引擎上意外缓慢

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

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

      有人会认为运算符会比方法调用更快,但事实证明,跨浏览器并不可靠。

    4. 标志方法:

      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 :(得分:15)

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

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

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

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

答案 2 :(得分:11)

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

results

但是如果考虑初始化时间,差异会消除,对象会比数组花费更多时间进行初始化。

enter image description here

答案 3 :(得分:0)

对象参考无疑是赢家

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

test array.indexOf(key) v. object[key]

这表明,object[key]运算符仅涉及1个键并且查找失败时,速度快〜 6倍

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

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

Object是明显的赢家。

答案 4 :(得分:0)

我清楚地看到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.
// ---------------------------------------------------------------------------------