По какому принципу цикл FOR IN обходит массив?

perfect

Меня интересует порядок обращения к элементам в массиве с помощью цикла FOR IN. А то есть с какого элемента начинает и каков принцип выборки.

2 ответа

perfect

Цикл for...in проходит через перечисляемые свойства объекта, в произвольном порядке

<span>for...in</span> не следует использовать для <span>Array</span>, где важен порядок индексов. Индексы массива перечисляемые свойства с целочисленными именами, а в остальном аналогичны объектам. Нет гарантии, что for...in будет возвращать индексы в нужном порядке и вернёт все перечисляемые свойства, включая имеющие нецелочислененные имена и наследуемые

Вдобавок, порядок обхода зависит от реализации ECMAScript браузером

стандарт ECMAScript оставляет порядок итерации по свойствам объектов на усмотрение реализующей стороны

Для массивов предпочтительней использовать простой for


perfect

tl;dr:

Сначала переберутся числовые ключи соответствующие индексам начиная с 0, затем все остальные строковые ключи непосредственно из объекта, затем из прототипа.

Немного спецификации

Для различных вариантов for..in вызывается одна и та же внутренняя функция

<i>IterationStatement: <b>for</b> <b>(</b> LeftHandSideExpression <b>in</b> Expression <b>)</b> Statement</i>
  1. Let keyResult be ? ForIn/OfHeadEvaluation(« », Expression, enumerate).
  2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, assignment, labelSet).
<i>IterationStatement: <b>for</b> <b>(</b> <b>var</b> ForBinding <b>in</b> Expression <b>)</b> Statement</i>
  1. Let keyResult be ? ForIn/OfHeadEvaluation(« », Expression, enumerate).
  2. Return ? ForIn/OfBodyEvaluation(ForBinding, Statement, keyResult, varBinding, labelSet).
<i>IterationStatement: <b>for ( </b>ForDeclaration <b>in</b> Expression <b>)</b> Statement</i>
  1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(BoundNames of ForDeclaration, Expression, enumerate).
  2. Return ? ForIn/OfBodyEvaluation(ForDeclaration, Statement, keyResult, lexicalBinding, labelSet).

Если перейти к этой функции можно найти алгоритм, который определяет получение свойств. Интересен пункт 6

<ol start="6"> <li>Если <em>iterationKind</em> is <span>enumerate</span>, то <ol> <li>Если <code>exprValue.[[Value]]</code> равен <code>null</code> или <code>undefined</code>, тогда <ul> <li>Вернуть <code>Completion{[[Type]]: break, [[Value]]: empty, [[Target]]: empty}</code>.</li> </ul> </li> <li>Пусть <span>obj</span> будет <code>ToObject(exprValue)</code>.</li> <li>Вернуть <em><span>EnumerateObjectProperties(obj)</span></em>.</li> </ol> </li> </ol>

Именно в функции EnumerateObjectProperties(obj) и осуществляется перебор.

В ее описании можно найти следующий пункт:

Возвращает объект итератора проходящего по всем <span>Строковым</span> ключам перебираемых свойств объекта O. Механика и порядок перечисления свойств не определен, но должен соответствовать правилам указанным ниже.

В нашем случае интересны следующие правила:

<ol> <li>The enumerable property names of prototype objects must be obtained by invoking EnumerateObjectProperties passing the prototype object as the argument.</li> <li>EnumerateObjectProperties must obtain the own property keys of the target object by calling its [[OwnPropertyKeys]] internal method. </li> </ol>
<ol> <li>Имена перечисляемых свойств прототипа должны быть получены вызовом <em>EnumerateObjectProperties</em> с передачей ей прототипа текущего объекта как аргумента.</li> <li><em>EnumerateObjectProperties</em> должен получить собственный имена свойств целевого объекта с помощью вызова внутреннего метода <em>[[OwnPropertyKeys]]</em></li> </ol>

И наконец, пришли к главному: [[OwnPropertyKeys]]. Если объект не строка или ********** вызывается OrdinaryOwnPropertyKeys (O) в которой выполняются следующие шаги:

<ol> <li><em>keys</em> = new empty <em>List</em>.</li> <li>Для каждого свойства <em>P</em>, из объекта <em>O</em>, которое является целым числом больше либо равным 0, в порядке возрастания: <ol> <li>Добавить <em>P</em> в конец <em>keys</em>.</li> </ol> </li> <li>Для каждого свойства <em>P</em>, из объекта <em>O</em>, которое является Строкой, но не целым числом больше либо равным 0, в порядке создания свойств <ol> <li>Добавить <em>P</em> в конец <em>keys</em>.</li> </ol> </li> <li>Для каждого свойства <em>P</em>, из объекта <em>O</em>, имеющего тип <em>Symbol</em>, в порядке создания <ol> <li>Добавить <em>P</em> в конец <em>keys</em>.</li> </ol> </li> <li>Вернуть <em>keys</em>.</li> </ol>

В итоге в тело for..in попадет сначала целочисленные ключи больше либо равные 0 в порядке возрастания, затем строковые, которые не могут быть представлены целым числом больше либо равным 0, в порядке добавления в объект. А так же после этого в том же порядке добавятся перечисляемые свойства из прототипа.

Можно отметить, что поведение массива, в данном случае ничем не отличается от поведения обычного объекта.

licensed under cc by-sa 3.0 with attribution.