Является ли индекс массива JavaScript строкой или целым числом?

У меня был общий вопрос о массивах JavaScript. Индексы массива в JavaScript внутренне обрабатываются как строки?

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


person user3033194    schedule 18.12.2014    source источник


Ответы (5)


Правильно так:

> var a = ['a','b','c']
undefined
> a
[ 'a', 'b', 'c' ]
> a[0]
'a'
> a['0']
'a'
> a['4'] = 'e'
'e'
> a[3] = 'd'
'd'
> a
[ 'a', 'b', 'c', 'd', 'e' ]
person Patrick Gunderson    schedule 18.12.2014
comment
Хорошо, я этого не знал. Благодарю вас! - person user3033194; 18.12.2014
comment
for (var i in a) console.log(typeof i) показывает "строку" для всех индексов. - person RobG; 18.12.2014
comment
Да, но [ 'a', 'b', 'c' ].map((_, i) => typeof i) возвращает [ 'number', 'number', 'number' ]. - person dbkaplun; 25.02.2018

Формально все имена свойств являются строками. Это означает, что числовые имена свойств, подобные массивам, на самом деле ничем не отличаются от любых других имен свойств.

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

Теперь внутренне среда выполнения JavaScript может свободно реализовывать функциональность массивов любым удобным для нее способом.

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

person Pointy    schedule 18.12.2014
comment
Любопытство сгубило кошку: не могли бы вы дать ссылку на это, пожалуйста? Я помню, что положительные целые числа ниже 2 ^ 32 были целыми числами, а все остальное - строковым хэш-поиском (хотя речь идет только о массиве).. - person GitaarLAB; 18.12.2014
comment
Да, видел, это было быстро - person GitaarLAB; 18.12.2014
comment
@GitaarLAB Я часто просматриваю спецификацию, поэтому она находится в списке завершения URL-адресов моего браузера прямо вверху :) - person Pointy; 18.12.2014
comment
@Pointy В этом случае целочисленный индекс следует обрабатывать как строку, поскольку он является свойством массива, который является особым типом объекта JS. - person user3033194; 18.12.2014
comment
@user3033194 user3033194 right - числовые значения, используемые в качестве ссылок на свойства с помощью оператора [ ], преобразуются в строки, или, по крайней мере, в спецификации указано, что шаг преобразования должен произойти. Вы дали мне идею, поэтому я расширим ответ. - person Pointy; 18.12.2014
comment
Хм, я прочитал вашу ссылку. Будучи сбитым с толку, я помню A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 23^2-1. из спецификации 15.4. -not-4294967295" title="почему индекс массива javascript не превышает 4294967294, но не 4294967295"> stackoverflow.com/questions/12766422/ Это изменилось? - person GitaarLAB; 18.12.2014
comment
@Pointy Большое спасибо!! - person user3033194; 18.12.2014
comment
@GitaarLAB Я думаю, дело в том, что это единственные, которые считаются настоящими индексами - другими словами, единственные, которые влияют на значение .length. - person Pointy; 18.12.2014
comment
@user3033194 user3033194 хорошо, что я хотел сделать, так это поиграть с Number.toString, но оказалось, что в особом случае преобразования числа в строку существует внутренний процесс, и фактическая функция .toString не вызывается. Это то, чего я не знал. - person Pointy; 18.12.2014
comment
«Настоящие» индексы - это «настоящие» массивы (для javascript). Таким образом, все остальное является свойствами, добавленными к объекту (который ссылается на массив). По крайней мере, таково было мое понимание. - person GitaarLAB; 18.12.2014
comment
@GitaarLAB верно - если подумать, единственная особенность массивов в JavaScript - это несколько волшебные вещи, которые происходят со свойством .length. - person Pointy; 18.12.2014
comment
Да, это моя точка зрения. Именно эта «магия» и отличает объект от массива (в терминах javascript). Другими словами, когда .length или new array(4294967300) перестает работать. Однако придирчиво, есть разница, или что я упускаю? Кроме того, что вернет array.push после переполнения 32-битного беззнакового? - person GitaarLAB; 18.12.2014
comment
@GitaarLAB: Попробуйте (предупреждение: это некрасиво): var arr = []; arr[4294967296] = 42; arr.push(43); console.dir(arr); - person Felix Kling; 18.12.2014
comment
@Felix: У меня сейчас слишком много открытой памяти, я искренне боюсь, что это приведет к сбою по крайней мере моего браузера.. Мне было бы любопытно, что делает на вашем компьютере :). Кроме того, вопрос касается индексов массива, а этот ответ говорит о свойствах объекта. В той же спецификации, на которую ссылается 15.4, в спецификации четко указано: A property name P (in the form of a String value) is an **array index** if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 2^32−1. В противном случае мы говорим о свойствах объекта. - person GitaarLAB; 18.12.2014
comment
@GitaarLAB: Сейчас. После arr[4294967294] = 42; arr.length правильно показывает 4294967295. Однако вызов arr.push(21); приводит к ошибке RangeError: Invalid array length. arr[arr.length] = 21 работает, но не меняет length. - person Felix Kling; 18.12.2014
comment
@ФеликсКлинг: +1. Спасибо за тестирование (и подтверждение моих убеждений)! Тем временем я печатал свой собственный ответ, надеюсь, с остальными ответами будущие посетители получат полную картину. - person GitaarLAB; 18.12.2014

Да, технически индексы массива — это строки, но, как элегантно выразился Фланаган в своем «Полном руководстве»: полезно четко отличать индекс массива от имени свойства объекта. Все индексы являются именами свойств, но только имена свойств, которые являются целыми числами от 0 до 232-1, являются индексами.

Обычно вам не следует заботиться о том, что браузер (или, в более общем смысле, «скрипт-хост») делает внутри, пока результат соответствует предсказуемому и (обычно/надеюсь) указанному результату. Фактически, в случае JavaScript (или ECMAScript 262) описывается только то, какие концептуальные шаги необходимы. Это (намеренно) оставляет место для скриптового хоста (и браузеров), чтобы придумать умный, меньший и более быстрый способ реализовать указанное поведение.

На самом деле, современные браузеры используют ряд различных алгоритмов для разных типов массивов внутри: важно, что они содержат, насколько они велики, упорядочены ли они, фиксированы ли и оптимизируются ли они во время (JIT) компиляции или они редкие или плотные (да, часто бывает выгодно делать new Array(length_val) вместо ниндзя []).

В вашей концепции мышления (при изучении JavaScript) может помочь знание того, что массивы — это просто особый вид объектов. Но они не всегда совпадают с ожидаемыми, например:

var a=[];
a['4294967295']="I'm not the only one..";
a['4294967296']="Yes you are..";
alert(a);  // === I'm not the only one..

хотя для неосведомленного программиста легко и довольно прозрачно иметь массив (с индексами) и прикреплять свойства к объекту-массиву.

Лучший ответ (я думаю) взят из спецификации (15.4) себя:

Объекты массива

Объекты-массивы придают особое значение определенному классу имен свойств. Имя свойства P (в виде строкового значения) является индексом массива тогда и только тогда, когда ToString(ToUint32(P)) равно P, а ToUint32(P) не равно 232< /sup>−1. Свойство, имя свойства которого является индексом массива, также называется элементом. Каждый объект Array имеет свойство длины, значение которого всегда является целым неотрицательным числом меньше 232. Значение свойства length численно больше, чем имя каждого свойства, имя которого является индексом массива; всякий раз, когда свойство объекта Array создается или изменяется, другие свойства настраиваются по мере необходимости, чтобы сохранить этот инвариант. В частности, всякий раз, когда добавляется свойство, имя которого является индексом массива, свойство длины при необходимости изменяется, чтобы быть на единицу больше, чем числовое значение этого индекса массива; и всякий раз, когда изменяется свойство длины, каждое свойство, имя которого является индексом массива, значение которого не меньше новой длины, автоматически удаляется. Это ограничение применяется только к собственным свойствам объекта Array и не зависит от свойств длины или индекса массива, которые могут быть унаследованы от его прототипов.

Объект O называется разреженным, если следующий алгоритм возвращает значение true:

  1. Пусть len будет результатом вызова внутреннего метода [[Get]] O с длиной аргумента.

  2. Для каждого целого числа i в диапазоне 0≤i‹ToUint32(len)

    а. Пусть elem будет результатом вызова внутреннего метода [[GetOwnProperty]] O с аргументом ToString(i). б. Если элемент не определен, вернуть true.

  3. Вернуть ложь.

Фактически спецификация ECMAScript 262 просто гарантирует программисту JavaScript однозначные ссылки на массивы независимо от получения/установки arr['42'] или arr[42] вплоть до 32-битного числа без знака.

Основное отличие состоит, например, в (автоматическом обновлении) array.length, array.push и других сахарных массивов, таких как array.concat и т. д. Хотя, да, JavaScript также позволяет зацикливаться на свойствах, установленных для объекта, мы не можем прочитать, сколько мы поставили (без петли). И да, насколько мне известно, современные браузеры (особенно Chrome в том, что они называют (но не указывают точно)) «маленькими целыми числами» очень быстро работают с настоящими (предварительно инициализированными) массивами малых целых чисел.

Также см., например, этот связанный вопрос.

Редактировать: согласно тесту @Felix Kling (из его комментария выше):

После arr[4294967294] = 42; arr.length правильно показывает 4294967295. Однако вызов arr.push(21); бросает RangeError: Invalid array length. arr[arr.length] = 21 работает, но не меняет длину.

Объяснение этого (предсказуемого и предполагаемого) поведения должно быть ясным после этого ответа.

Изменить2:

Теперь кто-то дал комментарий:

for (var i in a) console.log(typeof i) показывает «строку» для всех индексов.

Поскольку for in — это (неупорядоченный, который я должен добавить) итератор свойств в JavaScript, очевидно, что он возвращает строку (мне было бы чертовски чертовски, если бы это было не так).

Из MDN:

for..in не следует использовать для перебора массива, где важен порядок индексов.

Индексы массива — это просто перечисляемые свойства с целочисленными именами, в остальном они идентичны общим свойствам объекта. Нет никакой гарантии, что for...in вернет индексы в любом конкретном порядке и вернет все перечисляемые свойства, включая свойства с нецелочисленными именами и унаследованные.

Поскольку порядок итерации зависит от реализации, итерация по массиву может не посещать элементы в согласованном порядке. Поэтому лучше использовать цикл for с числовым индексом (или Array.forEach, или цикл for...of) при переборе массивов, где важен порядок доступа.

Итак.. что мы узнали? Если порядок важен для нас (часто с массивами), то нам нужен этот причудливый массив в JavaScript, а наличие «длины» весьма полезно для циклов в числовом порядке.

Теперь подумайте об альтернативе: дайте своим объектам идентификатор/порядок, но тогда вам нужно будет снова перебирать свои объекты для каждого следующего идентификатора/порядка (свойства)...

Редактировать 3:

Кто-то ответил в духе:

var a = ['a','b','c'];
a['4'] = 'e';
a[3] = 'd';
alert(a); // returns a,b,c,d,e

Теперь, используя объяснение в моем ответе: произошло то, что '4' можно привести к целому числу 4, и оно находится в диапазоне [0, 4294967295], что делает его допустимым массивом index, также называемым element. Поскольку var a является массивом ([]), массив элемент 4 добавляется как элемент массива, а не как свойство (что произошло бы, если бы var a был объектом ({} ).

Пример для дальнейшего описания разницы между массивом и объектом:

var a = ['a','b','c'];
a['prop']='d';
alert(a);

посмотрите, как он возвращает a,b,c без 'd'.

Редактировать 4:

Вы прокомментировали: В этом случае целочисленный индекс следует обрабатывать как строку, поскольку он является свойством массива, который является особым типом объекта JavaScript. Это неверно с точки зрения терминологии, потому что: (строки, представляющие) целочисленные индексы (между [0, 4294967295]) создают массив indexes или elements; не properties.

Лучше сказать: как фактическое целое число, и string, представляющее целое число (оба между [0, 4294967295]), являются действительным массивом index (и концептуально должны рассматриваться как целое число) и создает/изменяет массив элементов (например, "вещи"/значения (только), которые возвращаются, когда вы выполняете arr.join() или arr.concat()).

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

person GitaarLAB    schedule 18.12.2014
comment
Нет, и я не единственный, кто так говорит: из блога д-ра Акселя Раушмайера: array indices in JavaScript are actually strings. Naturally, engines perform optimizations under the hood so that, internally, that is not true. But it is how the spec defines them и Pretend array indices are numbers. That’s what usually happens under the hood and the general direction in which ECMAScript is moving. По сути, спецификация ECMAScript 262 просто обеспечивает пользователю однозначные ссылки на массивы независимо от получения/установки '9' или 9 до 32 бит без знака. - person GitaarLAB; 18.12.2014

В JavaScript есть два типа массивов: стандартные массивы и ассоциативные массивы (или объект со свойствами)

  • [ ] - стандартный массив - только целочисленные индексы на основе 0
  • { } — ассоциативный массив — объекты JavaScript, где ключами могут быть любые строки

So ...

var arr = [ 0, 1, 2, 3 ];

... определяется как стандартный массив, в котором индексы могут быть только целыми числами. Когда вы делаете arr["something"], поскольку что-то (то, что вы используете в качестве индекса) не является целым числом, вы в основном определяете свойство для объекта arr (в JavaScript все является объектом). Но вы не добавляете элемент в стандартный массив.

person rfornal    schedule 18.12.2014
comment
Объекты JavaScript во многом ведут себя как ассоциативные массивы, но на самом деле это не одно и то же, и спецификация никогда не использует эту терминологию. - person Pointy; 18.12.2014
comment
Я просто поправил использование этой терминологии. - person rfornal; 18.12.2014
comment
Вероятно, правильнее изображать массивы как тип объекта, а не наоборот. - person RobG; 18.12.2014

Давайте посмотрим:

[1]["0"] === 1 // true

О, но это не окончательно, поскольку среда выполнения может приводить "0" к +"0" и +"0" === 0.

[1][false] === undefined // true

Итак, +false === 0, так что нет, среда выполнения не приводит значение к числу.

var arr = [];
arr.false = "foobar";
arr[false] === "foobar" // true

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

person Witiko    schedule 18.12.2014
comment
Это совершенно новое для меня. Раньше я думал, что индекс массива JS похож на индексы массивов в других языках. - person user3033194; 18.12.2014
comment
Имейте в виду, что внутренне среда выполнения, скорее всего, будет представлять массив как традиционный массив для повышения производительности. Но для пользователя массив — это просто объект. - person Witiko; 18.12.2014