#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
.)
Итак, ваши варианты:
-
Ваш метод массива:
corporatePlan = [ 'canDeAuthorize', 'hasGmailSupport', 'canShareReports', 'canSummonKraken', 'etc' ]; console.log(corporatePlan.indexOf("canSummonKraken") >= 0); // true console.log(corporatePlan.indexOf("canDismissKraken") >= 0); // false
… что я бы не рекомендовал.
-
Метод
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» вообще существовали. Пустой объект представляет собой план, в котором пользователь ничего не может сделать, и он довольно мал.Здесь я должен отметить две вещи:
-
in
также проверяет прототип объекта, поэтому, если у вас есть настройки типаtoString
илиvalueOf
, вы получите ложные срабатывания (поскольку это свойства, которые получают почти все объектыObject.prototype
). В браузере с поддержкой ES5 вы можете избежать этой проблемы, создав свой объект сnull
прототипом:var corporatePlan = Object.create(null);
-
Возможно, из-за того, что он проверяет прототипы,
in
оператор на удивление медленный на некоторых движках.
Обе эти проблемы можно решить, используя
hasOwnProperty
вместо:console.log(corporatePlan.hasOwnProperty("canSummonKraken")); // true console.log(corporatePlan.hasOwnProperty("canDismissKraken")); // false
Можно было бы подумать, что оператор будет быстрее, чем вызов метода, но оказывается, что это ненадежно для кроссбраузерности.
-
-
Метод 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]
ссылкой и значением здесь.
Это показывает, что 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.
// ---------------------------------------------------------------------------------