Что в JS быстрее: оператор «in» объекта или indexof массива?

#javascript #performance

#javascript #Производительность

Вопрос:

Я хочу сохранить список строк, которые я буду проверять только на наличие, например:

 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, поскольку, конечно, «contains» — это постоянное время на карте и линейное для массива. Проверяет ли это, как массивы и объекты реализованы под капотом в JS?

В нашем конкретном случае, с менее чем 100 элементами, скорость не имеет большого значения. Но для большего массива какой способ будет быстрее при доступе?

Комментарии:

1. Не забывайте запятые после null s в вашем объектном литерале.

2. @Fish : FWIW, я обновил свой ответ, чтобы предложить третий вариант, который, я думаю, был бы довольно нормальным способом.

3. проверьте этот тест: jsben.ch/#/Y9jDP

Ответ №1:

В JavaScript вы обычно имеете дело с широким спектром реализаций (если только вы не используете его в контролируемой среде, такой как сервер, где вы выбираете движок), и поэтому ответ на конкретные вопросы о производительности, как правило, звучит так: «это зависит, проверьте это на движках, которые вы собираетесь использовать.» То, что быстрее в одной реализации, может быть медленнее в другой и т.д. http://jsperf.com это удобно для такого рода вещей.

Тем не менее, я ожидал in бы, что здесь я буду явным победителем. Array#indexOf должен обращаться к индексам массива при поиске, а индексы массива являются свойствами, как и любое другое свойство. Таким образом, доступ к индексу массива 0 , чтобы узнать, является ли это нужной строкой, требует поиска 0 точно так же, как для другого требуется поиск свойства "canSummonKraken" (а затем он должен выполнить сравнение строк после этого). (Да, индексы массива являются свойствами. Массивы в JavaScript на самом деле не являются массивами вообще.) И indexOf , возможно, придется обращаться к нескольким свойствам во время его поиска, тогда in как вам нужно будет обращаться только к одному. Но опять же, вам нужно будет проверить это в вашей целевой среде, чтобы убедиться, что некоторые реализации могут оптимизировать массивы, которые имеют непрерывные диапазоны индексов (но самые медленные определенно этого не делают, и, конечно, если вы беспокоитесь о скорости, вас беспокоит, что быстрее на самых медленных движках, таких как IE’s).

Также обратите внимание, что не все движки JavaScript еще имеют Array#indexOf . Большинство так и делают, но есть еще несколько старых (я смотрю на вас, Microsoft), которые этого не делают.

У вас также возникает вопрос о том, использовать in ли или hasOwnProperty . Преимущество использования in в том, что это оператор, а не вызов функции; преимущество использования hasOwnProperty в том, что он будет рассматривать только конкретный экземпляр объекта, а не его прототип (и его прототип и т. Д.). Если у вас нет очень глубоко унаследованной иерархии (а у вас ее нет в вашем примере),Я уверен in , что выигрывает, но полезно помнить, что он проверяет иерархию.

Кроме того, помните, что "canSummonKraken" in obj это будет верно в примере объектного литерала, который вы показали, потому что у объекта есть свойство, даже если значение свойства равно null. У вас вообще не должно быть свойства in, чтобы возвращать 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 также проверяет прототип объекта, поэтому, если у вас есть настройки типа toString или valueOf , вы получите ложные срабатывания (поскольку это свойства, которые получают почти все объекты Object.prototype ). В браузере с поддержкой ES5 вы можете избежать этой проблемы, создав свой объект с null прототипом: var corporatePlan = Object.create(null);

    2. Возможно, из-за того, что он проверяет прототипы, in оператор на удивление медленный на некоторых движках.

    Обе эти проблемы можно решить, используя hasOwnProperty вместо:

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

    Можно было бы подумать, что оператор будет быстрее, чем вызов метода, но оказывается, что это ненадежно для кроссбраузерности.

  3. Метод flags:

     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. 1, я согласен, не тестировал его, но in ему не нужно искать элементы, как это делает индексация.

Ответ №2:

Я проверил это: http://jsperf.com/array-indexof-vs-object-s-in-operator/4

При нахождении первого элемента оба имеют хорошие результаты в зависимости от используемого браузера. Таким образом, поиск последнего элемента, in оператор намного быстрее.

Но затем я использовал вариант с оператором typeof, который намного быстрее, чем оба:

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

Комментарии:

1. Почему typeof !== 'undefined' вместо !== undefined ?

2. typeof возвращает строку, а не объект; вы добавляете туда дополнительное принуждение. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference /…

3. @Nevir он имеет obj['that'] !== undefined в виду, что это намного быстрее, чем сравнение строк.

4. undefined не является константой и может быть перезаписан. Проверка намного безопаснее "undefined" .

5. @oncode Это уже не так в современных браузерах; более того, это было совершенно параноидально с самого начала. код typeof выглядит более запутанным.

Ответ №3:

Вот тест http://jsperf.com/array-indexof-vs-object-keys . В Chrome и Firefox проверка наличия ключа в объекте выполняется на 100% быстрее, чем сканирование массива.

Результаты

Но если вы принимаете во внимание время инициализации, разница сводится на нет, для инициализации объектов требуется намного больше времени, чем для массивов.

введите описание изображения здесь

Комментарии:

1. Для корректного тестирования вы должны «удалить» время инициализации: в обоих случаях инициализируются две сущности — объект и массив. И затем в 1-м случае тестируйте только массив, а во 2-м — объект.

Ответ №4:

Ссылка на объект — явный победитель

Я провел гораздо более простой тест, который на самом деле проверяет только array.indexOf операцию: и сравнивает со object[key] ссылкой и значением здесь.

тестовый массив.indexOf(ключ) v. объект [ключ]

Это показывает, что object[key] оператор работает в ~ 6 раз быстрее, когда задействован только 1 ключ, и поиск завершается неудачно.

Это действительно поиск в худшем случае (сбой) в наборе данных наилучшего случая (1 запись), который должен служить хорошим прокси для большинства из этих типов операций.

Разрыв в скорости увеличивается с каждым ключом, добавляемым к источнику Array a / Object o .

Object ссылка — явный победитель.

Ответ №5:

я вижу логическое значение (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.
// ---------------------------------------------------------------------------------