Массивы ключей number и “number” неожиданно считаются одинаковыми
-
19-08-2019 - |
Вопрос
Я играл с массивами javascript, и я столкнулся, как мне кажется, с некоторыми несоответствиями, я надеюсь, что кто-нибудь сможет объяснить их для меня.
Давайте начнем с этого:
var myArray = [1, 2, 3, 4, 5];
document.write("Length: " + myArray.length + "<br />");
for( var i in myArray){
document.write( "myArray[" + i + "] = " + myArray[i] + "<br />");
}
document.write(myArray.join(", ") + "<br /><br />");
Length: 5 myArray[0] = 1 myArray[1] = 2 myArray[2] = 3 myArray[3] = 4 myArray[4] = 5 1, 2, 3, 4, 5
В этом коде нет ничего особенного, но я понимаю, что массив javascript - это объект, поэтому свойства могут быть добавлены в массив, способ добавления этих свойств в массив кажется мне непоследовательным.
Прежде чем продолжить, позвольте мне отметить, как строковые значения должны быть преобразованы в числовые значения в javascript.
Непустая строка -> Числовое значение строки или NaN
Пустая строка -> 0
Итак, поскольку массив javascript является объектом, следующее является законным:
myArray["someThing"] = "someThing";
myArray[""] = "Empty String";
myArray["4"] = "four";
for( переменная i в myArray){
document.write( "myArray[" + i + "] = " + myArray[i] + "<br />");}
document.write(Мой массив.join(", ") + "<br /><br />");
Length: 5 myArray[0] = 1 myArray[1] = 2 myArray[2] = 3 myArray[3] = 4 myArray[4] = four myArray[someThing] = someThing myArray[] = Empty String 1, 2, 3, 4, four
Результат получается неожиданным.
Непустая строка "4" преобразуется в ее числовое значение при установке свойства myArray["4"], это кажется правильным.Однако пустая строка "" не преобразуется в свое числовое значение 0, она обрабатывается как пустая строка.Также непустая строка "something" не преобразуется в свое числовое значение NaN, она обрабатывается как строка.Так что же это такое?является ли оператор внутри myArray[] в числовом или строковом контексте?
Кроме того, почему два нечисловых свойства myArray не включены в myArray.length и myArray.join(", ")?
Решение
Ключи массива JavaScript на самом деле являются строками.Для получения подробной информации и реализации типа карты для произвольных ключей проверьте этот ответ.
Чтобы уточнить и добавить к тому, что опубликовал Джейсон:Массивы JavaScript - это объекты.Объекты обладают свойствами.Имя свойства - это строковое значение.Следовательно, индексы массива также преобразуются в строки, прежде чем может произойти что-либо еще.Имя свойства P будет обрабатываться как индекс массива (т. е. будет вызвана специальная магия массива), если выполняется следующее (ECMA-262, 15.4):
toString(ToUint32(P)) равно P, а ToUint32(P) не равно 2^ 32 − 1
То, что числовые индексы будут преобразованы в строки (а не наоборот), можно легко проверить:
var array = [];
array[1] = 'foo';
array['1'] = 'bar';
array['+1'] = 'baz';
document.writeln(array[1]); // outputs bar
Кроме того, это плохая практика - перебирать записи массива с for..in
цикл - вы можете получить неожиданные результаты, если кто-то напортачит с некоторыми прототипами (и это тоже не очень быстро).Используйте стандартный for(var i= 0; i < array.length; ++i)
вместо этого.
Другие советы
(редактировать:следующее не совсем верно)
Ключи JavaScript - файла Объект на самом деле это строки.A Javascript - файл Массив сам по себе имеет числовые индексы.Если вы храните что-то с индексом, который может быть интерпретирован как неотрицательное целое число, оно попытается это сделать.Если вы храните что-то с индексом, который не является неотрицательным целым числом (напримерэто буквенно-цифровое, отрицательное число или число с плавающей запятой с дробной частью), произойдет сбой в хранилище с индексом массива, и по умолчанию Объект (который является базовым классом Array) хранит, который затем преобразует аргумент в строку и сохраняет по строковому индексу - но эти сохраненные свойства не видны классу Array и, следовательно, не видны его методам / свойствам (length, join, slice, splice, push, pop и т.д.).
Редактировать:приведенное выше не совсем верно (как показывает пример foo / bar / baz Кристофера).Фактические показатели хранения в соответствии с Спецификация ECMAScript являются, по сути, строками, но если они являются допустимыми индексами массива (неотрицательными целыми числами), то объект массива [[Put]]
метод, который является специальным, делает эти конкретные значения видимыми для методов массива "array-ish".
Это ответ на Пост Филона.Его бенчмарк ошибочен, потому что он использует разные имена свойств для версии объекта:Он должен был использовать i
так же хорошо, как и не x
.
Если все сделано правильно, например, вот так:
var start, end, count = 1000000;
var obj = {},
array = [];
start = new Date;
for(var i = count; i--; )
array[i] = i;
end = new Date;
document.writeln(Number(end) - Number(start));
start = new Date;
for(var i = count; i--; )
obj[i] = i;
end = new Date;
document.writeln(Number(end) - Number(start));
Вы увидите, что эти времена будут очень близки.В FF3.0.5 версия array работает еще медленнее (в Opera все наоборот).
Массивы, как и все остальное в JavaScript, являются объектами.Объекты были благословлены точечная нотация чтобы облегчить нагрузку на разработчиков.Используя ваш пример
myArray["someThing"] = "someThing";
это то же самое, что писать
myArray.someThing = "someThing";
В этом случае вы добавляете свойство к объекту вместо того, чтобы добавлять его в массив.То же самое относится и к пустой строке, хотя вы не можете использовать точечная нотация для пустой строки ... странно, да?
В случае "4" оно может быть преобразовано в целое число и, следовательно, используется в качестве индекса в массиве.
Я не согласен с Кристофом, когда он утверждает, что "индексы массива преобразуются в строки".
Во-первых, я думаю, что это зависит от реализации...Я полагаю, (хорошие) разработчики оптимизируют доступ к массиву, есть несколько разумных способов сделать это.
На самом деле, я провел небольшой тест, и хотя он так же хорош, как и большинство микро-бенчмарков (т.е.не супер-надежный), это интересно:
result = ""
var x;
var trueArray = []
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
x = "i" + i; // To do the same operations
trueArray[i] = 1;
}
var endTime = new Date();
result += "With array: " + (endTime - startTime) + "\n";
var sArray = []
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
x = "" + i;
sArray[x] = 1;
}
var endTime = new Date();
result += "With s array: " + (endTime - startTime) + "\n";
var objArray = {}
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
x = "i" + i;
objArray[x] = 1;
}
var endTime = new Date();
result += "With object(i): " + (endTime - startTime) + "\n";
var sobjArray = {}
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
x = "" + i;
sobjArray[x] = 1;
}
var endTime = new Date();
result += "With s object: " + (endTime - startTime) + "\n";
var iobjArray = {}
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
x = "" + i;
iobjArray[i] = 1;
}
var endTime = new Date();
result += "With i object: " + (endTime - startTime) + "\n";
// Then display result
В IE6 я получаю:С массивом:1453 С объектом:3547
На FF 3.0 я получаю:С массивом:83 С объектом:226
В Safari 3.1 я получаю:С массивом:140 С объектом:313
В Opera 9.26 по какой-то причине я не получаю результат, но если я уменьшу до десятой части количество циклов, я получу:С массивом:47 С объектом:516
На самом деле, я позволил Opera запуститься, пока набирал это, и, наконец, получил результат:С массивом:281 С объектом:166063...
Итак, массивы оптимизированы!И это к счастью...
Демонстрация Кристофа не произвела на меня впечатления.Мой вывод был бы скорее таким, что строки, которые могут быть интерпретированы как числа, обрабатываются как таковые, которые соответствуют приведенной в кавычках формуле...
Итак, моя интерпретация ваших результатов заключается в том, что массив ведет себя как быстрый массив с числовыми индексами при подаче с ними (возможно, с поведением ассоциативного массива при разреженных значениях, т. е.некоторые изолированные большие индексы), но как объект он по-прежнему имеет обычную обработку свойств.Но эти свойства не обрабатываются в части массива, отсюда и результат, который вы получили с помощью join().
[РЕДАКТИРОВАТЬ] Я добавил несколько циклов, следуя идее Кристофа.
На FF3 я получаю:С массивом:92 С массивом s:93 С объектом (i):243 С объектом s:194 С i объектом:125 (показатели варьируются между запусками, но примерно одинаковы).
Я не очень убежден в этом переходе integer -> string -> integer туда и обратно, даже в том, что ECMA запрашивает эту последовательность.То, как я это читаю, таково:если свойство является строкой и может быть интерпретировано как целое число, то оно обрабатывается как таковое.
Конечно, единственный верный способ узнать это - это посмотреть на реализацию...
Я с интересом замечаю, что простые объекты, получающие целочисленное свойство или свойство, которое может быть преобразовано в целое число, каким-то образом оптимизированы.Возможно, потому, что многие программисты JS использовали простые объекты в виде массивов, поэтому разработчики сочли интересным оптимизировать этот случай.