Массивы ключей номер и «номер» неожиданно считаются одинаковыми - PullRequest
4 голосов
/ 17 января 2009

Я играл с массивами 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 является объектом, допустимо следующее:

<code>
myArray["someThing"] = "someThing";
myArray[""] = "Empty String";
myArray["4"] = "four";</p>

<p>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] = four
myArray[someThing] = someThing
myArray[] = Empty String
1, 2, 3, 4, four

Вывод неожиданный.

Непустая строка "4" преобразуется в числовое значение при установке свойства myArray ["4"], это кажется правильным. Однако пустая строка "" не преобразуется в ее числовое значение 0, она рассматривается как пустая строка. Также непустая строка «что-то» не преобразуется в его числовое значение, NaN, оно рассматривается как строка. Так что это? находится оператор внутри myArray [] в числовом или строковом контексте?

Кроме того, почему две нечисловые свойства myArray не включены в myArray.length и myArray.join (",")?

Ответы [ 5 ]

12 голосов
/ 17 января 2009

Ключи массива 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).

2 голосов
/ 18 января 2009

Это ответ на сообщение Филио . Его эталонный тест некорректен, потому что он использует разные имена свойств для версии объекта: он должен был использовать 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 версия массива даже постоянно медленнее (в Opera это наоборот).

2 голосов
/ 17 января 2009

(редактировать: следующее не совсем верно)

Ключи JavaScript Object на самом деле являются строками. Javascript Array сам по себе имеет числовые индексы. Если вы храните что-то с индексом, который может быть интерпретирован как неотрицательное целое число, он попытается это сделать. Если вы сохраняете что-то с индексом, который не является неотрицательным целым числом (например, это буквенно-цифровое, отрицательное число или число с плавающей запятой с дробной частью), это приведет к сбою в хранилище индекса массива, и по умолчанию будет Object (это базовый класс Array) хранилище, которое затем преобразует аргумент в строку и сохраняет по строковому индексу - но эти сохраненные свойства не видны классом Array и, следовательно, не видны его методам / свойствам (длина , объединить, нарезать, соединить, нажать, всплыть и т. д.).

edit: вышеприведенное не совсем верно (как показывает пример Кристофера foo / bar / baz). Фактические индексы хранения в соответствии со спецификацией ECMAscript на самом деле являются строками, но если они являются действительными индексами массива (неотрицательными целыми числами), то метод [[Put]] объекта Array, который является специальным, принимает эти конкретные значения видимый для методов массива "ish".

0 голосов
/ 18 января 2009

Массивы, как и все остальное в JavaScript, являются объектами. Объекты были благословлены точечной нотацией , чтобы облегчить нагрузку на разработчиков. Используя ваш пример

myArray["someThing"] = "someThing";

совпадает с написанием

myArray.someThing = "someThing";

В этом случае вы добавляете свойство к объекту, а не добавляете его в массив. То же самое относится и к пустой строке, хотя вы не можете использовать точечную запись для пустой строки ... странно, да?

В случае «4» оно может быть приведено к целому числу и, следовательно, используется как индекс в массиве.

0 голосов
/ 18 января 2009

Я не согласен с Кристофом, когда он заявляет, что «индексы массивов преобразуются в строки».

Во-первых, я думаю, что это зависит от реализации ... Полагаю, (хорошие) разработчики оптимизируют доступ к массиву, есть несколько умных способов сделать это.

На самом деле я провел небольшой тест, и хотя он так же хорош, как и большинство микро-тестов (т.е. не супер-надежный), он интересен:

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 (perfs варьируются между прогонами, но примерно соответствуют).

Я не очень убежден в этом целом числе -> строка -> целое число в оба конца, даже если ECMA запрашивает эту последовательность. Я прочел это следующим образом: если свойство является строкой и может быть интерпретировано как целое число, тогда оно рассматривается как таковое.

Конечно, единственный верный способ узнать это - посмотреть на реализацию ...

Я с интересом отмечаю, что обычные объекты, получающие целочисленное свойство или свойство, которое можно преобразовать в целое число, каким-то образом оптимизируются. Возможно, потому что многие программисты JS использовали простые объекты в качестве массивов, поэтому разработчики посчитали интересным оптимизировать этот случай.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...