Массивы ключей number и “number” неожиданно считаются одинаковыми

StackOverflow https://stackoverflow.com/questions/453765

Вопрос

Я играл с массивами 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 использовали простые объекты в виде массивов, поэтому разработчики сочли интересным оптимизировать этот случай.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top