Почему использование "for...in" с итерацией массива - плохая идея?
Мне сказали не использовать for...in
с массивами в JavaScript. Почему бы и нет?
28 ответов
Причина в том, что одна конструкция:
var a = []; // Create a new empty array.
a[5] = 5; // Perfectly legal JavaScript that resizes the array.
for (var i = 0; i < a.length; i++) {
// Iterate over numeric indexes from 0 to 5, as everyone expects.
console.log(a[i]);
}
/* Will display:
undefined
undefined
undefined
undefined
undefined
5
*/
иногда может полностью отличаться от других:
var a = [];
a[5] = 5;
for (var x in a) {
// Shows only the explicitly set index of "5", and ignores 0-4
console.log(x);
}
/* Will display:
5
*/
Также учтите, что библиотеки JavaScript могут делать такие вещи, которые влияют на любой созданный вами массив:
// Somewhere deep in your JavaScript library...
Array.prototype.foo = 1;
// Now you have no idea what the below code will do.
var a = [1, 2, 3, 4, 5];
for (var x in a){
// Now foo is a part of EVERY array and
// will show up here as a value of 'x'.
console.log(x);
}
/* Will display:
0
1
2
3
4
foo
*/
for-in
Сам по себе оператор не является "плохой практикой", однако его можно использовать неправильно, например, для перебора массивов или объектов, подобных массивам.
Цель for-in
утверждение состоит в перечислении свойств объекта. Это утверждение будет идти вверх по цепочке прототипов, также перечисляя наследуемые свойства, что иногда нежелательно.
Кроме того, порядок итерации не гарантируется спецификацией, что означает, что если вы хотите "перебрать" объект массива, с помощью этого оператора вы не можете быть уверены, что свойства (индексы массива) будут посещаться в числовом порядке.
Например, в JScript (IE <= 8) порядок перечисления даже для объектов Array определяется по мере создания свойств:
var array = [];
array[2] = 'c';
array[1] = 'b';
array[0] = 'a';
for (var p in array) {
//... p will be "2", "1" and "0" on IE
}
Кроме того, если говорить о наследуемых свойствах, если вы, например, расширяете Array.prototype
объект (как некоторые библиотеки, как MooTools), эти свойства также будут перечислены:
Array.prototype.last = function () { return this[this.length-1]; };
for (var p in []) { // an empty array
// last will be enumerated
}
Как я уже говорил, для перебора массивов или объектов, подобных массивам, лучше всего использовать последовательный цикл, такой как обычный старый for
/while
петля.
Если вы хотите перечислить только собственные свойства объекта (те, которые не наследуются), вы можете использовать hasOwnProperty
метод:
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
// prop is not inherited
}
}
А некоторые люди даже рекомендуют вызывать метод напрямую из Object.prototype
чтобы избежать проблем, если кто-то добавляет свойство с именем hasOwnProperty
к нашему объекту:
for (var prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// prop is not inherited
}
}
Есть три причины, почему вы не должны использовать for..in
перебрать элементы массива:
for..in
будет перебирать все собственные и унаследованные свойства объекта массива, которые не являютсяDontEnum
; это означает, что если кто-то добавляет свойства к конкретному объекту массива (для этого есть веские причины - я сам это сделал) или изменилArray.prototype
(что считается плохой практикой в коде, который должен хорошо работать с другими сценариями), эти свойства также будут повторяться; унаследованные свойства могут быть исключены путем проверкиhasOwnProperty()
, но это не поможет вам со свойствами, установленными в самом объекте массиваfor..in
не гарантируется сохранение порядка элементовэто медленно, потому что вы должны пройти все свойства объекта массива и всей его цепочки прототипов и все равно получите только имя свойства, т.е. чтобы получить значение, потребуется дополнительный поиск
Потому что for... in перечисляет объект, который содержит массив, а не сам массив. Если я добавлю функцию в цепочку прототипов массивов, она также будет включена. Т.е.
Array.prototype.myOwnFunction = function() { alert(this); }
a = new Array();
a[0] = 'foo';
a[1] = 'bar';
for(x in a){
document.write(x + ' = ' + a[x]);
}
Это напишет:
0 = foo 1 = бар myOwnFunction = function() { alert(this); }
И так как вы никогда не можете быть уверены, что ничего не будет добавлено в цепочку прототипов, просто используйте цикл for для перечисления массива:
for(i=0,x=a.length;i<x;i++){
document.write(i + ' = ' + a[i]);
}
Это напишет:
0 = foo 1 = бар
С 2016 года (ES6) мы можем использовать for…of
для итерации массива, как уже заметил Джон Слегерс.
Я просто хотел бы добавить этот простой демонстрационный код, чтобы прояснить ситуацию:
Array.prototype.foo = 1;
var arr = [];
arr[5] = "xyz";
console.log("for...of:");
var count = 0;
for (var item of arr) {
console.log(count + ":", item);
count++;
}
console.log("for...in:");
count = 0;
for (var item in arr) {
console.log(count + ":", item);
count++;
}
Консоль показывает:
for...of:
0: undefined
1: undefined
2: undefined
3: undefined
4: undefined
5: xyz
for...in:
0: 5
1: foo
Другими словами:
for...of
считает от 0 до 5, а также игнорируетArray.prototype.foo
, Показывает значения массива.for...in
перечисляет только5
, игнорируя неопределенные индексы массива, но добавляяfoo
, Он показывает имена свойств массива.
Краткий ответ: это просто не стоит.
Более длинный ответ: это просто не стоит того, даже если последовательный порядок элементов и оптимальная производительность не требуются.
Длинный ответ: Это просто не стоит, по следующим причинам:
- С помощью
for (var i in array) {}
вызовет интерпретацию "массива" как любого другого чистого объекта, обходя цепочку свойств объекта и, в конечном итоге, работая медленнее, чем на основе индексаfor
петля. - Не гарантируется возвращать свойства объекта в последовательном порядке, как можно было бы ожидать.
- С помощью
hasOwnProperty()
или жеisNaN()
проверка фильтрации свойств объекта - это дополнительная нагрузка, которая заставляет его работать (даже больше) медленнее. Кроме того, введение такой дополнительной логики сводит на нет основную причину ее использования, т. Е. Из-за более краткого формата.
По этим причинам приемлемого компромисса между производительностью и удобством даже не существует. На самом деле, нет никакой пользы, если только цель не состоит в том, чтобы рассматривать массив как чистый объект и выполнять операции над свойствами объекта массива.
В изоляции нет ничего плохого в использовании for-in для массивов. For-in перебирает имена свойств объекта, а в случае массива "из коробки" свойства соответствуют индексам массива. (Встроенные свойства, такие как length
, toString
и так далее не входят в итерацию.)
Однако если ваш код (или используемая вами среда) добавляет пользовательские свойства к массивам или к прототипу массива, то эти свойства будут включены в итерацию, что, вероятно, не то, что вам нужно.
Некоторые JS-фреймворки, такие как Prototype, модифицируют прототип Array. Другие фреймворки, такие как JQuery, этого не делают, поэтому с JQuery вы можете безопасно использовать for-in.
Если у вас есть сомнения, вы, вероятно, не должны использовать for-in.
Альтернативный способ перебора массива заключается в использовании цикла for:
for (var ix=0;ix<arr.length;ix++) alert(ix);
Однако у этого есть другая проблема. Проблема в том, что массив JavaScript может иметь "дыры". Если вы определите arr
как:
var arr = ["hello"];
arr[100] = "goodbye";
Тогда у массива есть два элемента, но длина 101. Использование for-in даст два индекса, в то время как цикл for даст 101 индекс, где значение 99 имеет значение undefined
,
В дополнение к причинам, приведенным в других ответах, вы, возможно, не захотите использовать структуру "for...in", если вам нужно выполнить математику с переменной counter, потому что цикл повторяет имена свойств объекта и, следовательно, переменной это строка
Например,
for (var i=0; i<a.length; i++) {
document.write(i + ', ' + typeof i + ', ' + i+1);
}
напишет
0, number, 1
1, number, 2
...
в то время как,
for (var ii in a) {
document.write(i + ', ' + typeof i + ', ' + i+1);
}
напишет
0, string, 01
1, string, 11
...
Конечно, это можно легко преодолеть, включив
ii = parseInt(ii);
в цикле, но первая структура более прямая.
Помимо того, что for
...in
перебирает все перечисляемые свойства (что не совпадает с "всеми элементами массива"!), см. http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf, раздел 12.6.4 (5-е издание) или 13.7.5.15 (7-е издание):
Механика и порядок перечисления свойств... не уточняется...
(Акцент мой.)
Это означает, что если браузер хочет, он может просматривать свойства в том порядке, в котором они были вставлены. Или по порядку номеров. Или в лексическом порядке (где "30" предшествует "4"! Имейте в виду, что все ключи объектов - и, следовательно, все индексы массивов - на самом деле являются строками, так что это имеет смысл). Он может проходить через них, если реализует объекты в виде хеш-таблиц. Или возьмите что-нибудь из этого и добавьте "назад". Браузер может даже выполнять итерации случайным образом и быть совместимым с ECMA-262, если он посещает каждое свойство ровно один раз.
На практике большинство браузеров в настоящее время любят выполнять итерации примерно в одном и том же порядке. Но ничто не говорит, что они должны. Это зависит от реализации и может измениться в любое время, если будет найден другой способ, который будет гораздо более эффективным.
В любом случае, for
...in
не несет с собой никакого смысла порядка. Если вы заботитесь о порядке, будьте откровенны с ним и используйте регулярный for
цикл с индексом.
Главным образом две причины:
Один
Как уже говорили другие, вы можете получить ключи, которых нет в вашем массиве или которые унаследованы от прототипа. Так что если, скажем, библиотека добавляет свойство к прототипам Array или Object:
Array.prototype.someProperty = true
Вы получите его как часть каждого массива:
for(var item in [1,2,3]){
console.log(item) // will log 1,2,3 but also "someProperty"
}
Вы можете решить это с помощью метода hasOwnProperty:
var ary = [1,2,3];
for(var item in ary){
if(ary.hasOwnProperty(item)){
console.log(item) // will log only 1,2,3
}
}
но это верно для итерации любого объекта с циклом for-in.
Два
Обычно порядок элементов в массиве важен, но цикл for-in не обязательно будет повторяться в правильном порядке, потому что он обрабатывает массив как объект, как это реализовано в JS, а не как массив. Это кажется небольшой вещью, но она действительно может испортить приложения и ее трудно отладить.
Я не думаю, что мне есть что добавить, например. Ответ Триптиха или ответ CMS о том, зачем использоватьfor-in
следует избегать в некоторых случаях.
Однако я хотел бы добавить, что в современных браузерах есть альтернатива for-in
которые могут быть использованы в тех случаях, когдаfor-in
не может быть использован. Эта альтернативаfor-of
:
for (var item of items) {
console.log(item);
}
Замечания:
К сожалению, ни одна версия Internet Explorer не поддерживает эту функцию ( Edge 12+), поэтому вам придется немного подождать, пока вы не сможете использовать ее в своем рабочем коде на стороне клиента. Тем не менее, он должен быть безопасным для использования в вашем JS-коде на стороне сервера (если вы используете Node.js).
Потому что он перечисляет через поля объекта, а не индексы. Вы можете получить значение с индексом "длина", и я сомневаюсь, что вы хотите этого.
Проблема с for ... in ...
- и это становится проблемой только тогда, когда программист на самом деле не понимает язык; на самом деле это не ошибка или что-то в этом роде - это то, что он перебирает все элементы объекта (ну, все перечисляемые элементы, но пока это подробно). Когда вы хотите перебрать только индексированные свойства массива, единственный гарантированный способ сохранить семантически непротиворечивые вещи - это использовать целочисленный индекс (то есть for (var i = 0; i < array.length; ++i)
петля стиля).
Любой объект может иметь произвольные свойства, связанные с ним. В частности, не было бы ничего страшного в загрузке дополнительных свойств в экземпляр массива. Код, который хочет видеть только индексированные свойства массива, должен придерживаться целочисленного индекса. Код, который полностью осознает, что for ... in
и действительно нужно видеть все свойства, ну тогда это тоже нормально.
TL&DR: использование for in
Цикл в массивах не является злом, на самом деле совсем наоборот.
я думаю for in
loop - это драгоценный камень JS, если он используется правильно в массивах. От вас ожидают, что вы полностью контролируете свое программное обеспечение и знаете, что делаете. Давайте посмотрим на упомянутые недостатки и опровергаем их один за другим.
- Он также перебирает наследуемые свойства: прежде всего любые расширения
Array.prototype
должно было быть сделано с помощьюObject.defineProperty()
и ихenumerable
дескриптор должен быть установлен вfalse
, Любая библиотека, не делающая этого, не должна использоваться вообще. - Свойства, которые вы добавляете в цепочку наследования, впоследствии учитываются: при выполнении подклассификации массива
Object.setPrototypeOf
или по классуextend
, Вы должны снова использоватьObject.defineProperty()
который по умолчанию устанавливаетwritable
,enumerable
а такжеconfigurable
дескрипторы свойств дляfalse
, Давайте посмотрим пример подклассификации массива здесь...
function Stack(...a){
var stack = new Array(...a);
Object.setPrototypeOf(stack, Stack.prototype);
return stack;
}
Stack.prototype = Object.create(Array.prototype); // now stack has full access to array methods.
Object.defineProperty(Stack.prototype,"constructor",{value:Stack}); // now Stack is a proper constructor
Object.defineProperty(Stack.prototype,"peak",{value: function(){ // add Stack "only" methods to the Stack.prototype.
return this[this.length-1];
}
});
var s = new Stack(1,2,3,4,1);
console.log(s.peak());
s[s.length] = 7;
console.log("length:",s.length);
s.push(42);
console.log(JSON.stringify(s));
console.log("length:",s.length);
for(var i in s) console.log(s[i]);
Итак, вы видите.. for in
Цикл теперь безопасен, так как вы заботились о своем коде.
-
for in
петля медленная: Ад нет. Это самый быстрый метод итерации, если вы зацикливаетесь на разреженных массивах, которые время от времени необходимы. Это один из самых важных приемов производительности, которые нужно знать. Давайте посмотрим на пример. Мы будем зацикливаться на разреженном массиве.
var a = [];
a[0] = "zero";
a[10000000] = "ten million";
console.time("for loop on array a:");
for(var i=0; i < a.length; i++) a[i] && console.log(a[i]);
console.timeEnd("for loop on array a:");
console.time("for in loop on array a:");
for(var i in a) a[i] && console.log(a[i]);
console.timeEnd("for in loop on array a:");
Вот причины, по которым это (обычно) плохая практика:
for...in
циклы перебирают все свои перечисляемые свойства и перечисляемые свойства своих прототипов. Обычно в итерации массива мы хотим перебирать только сам массив. И даже если вы сами ничего не можете добавить в массив, ваши библиотеки или фреймворк могут что-то добавить.
Пример:
Array.prototype.hithere = 'hithere';
var array = [1, 2, 3];
for (let el in array){
// the hithere property will also be iterated over
console.log(el);
}
for...in
циклы не гарантируют определенный порядок итераций. Хотя этот порядок обычно наблюдается в большинстве современных браузеров, гарантия 100% все еще отсутствует.for...in
петли игнорироватьundefined
элементы массива, т.е. элементы массива, которые еще не были назначены.
Пример:
const arr = [];
arr[3] = 'foo'; // resize the array to 4
arr[4] = undefined; // add another element with value undefined to it
// iterate over the array, a for loop does show the undefined elements
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
console.log('\n');
// for in does ignore the undefined elements
for (let el in arr) {
console.log(arr[el]);
}
Кроме того, из-за семантики, способ for, in
относится к массивам (т. е. так же, как к любому другому объекту JavaScript), не выровненному с другими популярными языками.
// C#
char[] a = new char[] {'A', 'B', 'C'};
foreach (char x in a) System.Console.Write(x); //Output: "ABC"
// Java
char[] a = {'A', 'B', 'C'};
for (char x : a) System.out.print(x); //Output: "ABC"
// PHP
$a = array('A', 'B', 'C');
foreach ($a as $x) echo $x; //Output: "ABC"
// JavaScript
var a = ['A', 'B', 'C'];
for (var x in a) document.write(x); //Output: "012"
В дополнение к другим проблемам, синтаксис for..in, вероятно, медленнее, поскольку индекс представляет собой строку, а не целое число.
var a = ["a"]
for (var i in a)
alert(typeof i) // 'string'
for (var i = 0; i < a.length; i++)
alert(typeof i) // 'number'
for
/in
работает с двумя типами переменных: хеш-таблицы (ассоциативные массивы) и массив (неассоциативные).
JavaScript автоматически определит способ прохождения элементов. Так что, если вы знаете, что ваш массив действительно неассоциативен, вы можете использовать for (var i=0; i<=arrayLen; i++)
и пропустите итерацию автоопределения.
Но на мой взгляд, лучше использовать for
/in
процесс, необходимый для этого автоопределения, очень мал.
Реальный ответ на этот вопрос будет зависеть от того, как браузер анализирует / интерпретирует код JavaScript. Это может измениться между браузерами.
Я не могу думать о других целях, чтобы не использовать for
/in
;
//Non-associative
var arr = ['a', 'b', 'c'];
for (var i in arr)
alert(arr[i]);
//Associative
var arr = {
item1 : 'a',
item2 : 'b',
item3 : 'c'
};
for (var i in arr)
alert(arr[i]);
Важным аспектом является то, что for...in
перебирает только свойства, содержащиеся в объекте, для атрибута перечисляемого свойства которого установлено значение true. Так что если кто-то пытается перебрать объект с помощью for...in
тогда произвольные свойства могут быть пропущены, если их атрибут перечислимого свойства равен false. Вполне возможно изменить атрибут перечислимого свойства для обычных объектов Array, чтобы определенные элементы не были перечислены. Хотя в целом атрибуты свойств обычно применяются к свойствам функций внутри объекта.
Можно проверить значение атрибута перечисляемого свойства свойств с помощью:
myobject.propertyIsEnumerable('myproperty')
Или получить все четыре атрибута свойства:
Object.getOwnPropertyDescriptor(myobject,'myproperty')
Эта функция доступна в ECMAScript 5 - в более ранних версиях было невозможно изменить значение атрибута перечисляемого свойства (для него всегда было установлено значение true).
Потому что он будет перебирать свойства, принадлежащие объектам вверх по цепочке прототипов, если вы не будете осторожны.
Ты можешь использовать for.. in
, просто обязательно проверьте каждое свойство с hasOwnProperty.
Это не обязательно плохо (в зависимости от того, что вы делаете), но в случае массивов, если что-то было добавлено в Array.prototype
тогда вы получите странные результаты. Где вы ожидаете, что этот цикл будет выполняться три раза:
var arr = ['a','b','c'];
for (var key in arr) { ... }
Если функция называется helpfulUtilityMethod
был добавлен в Array
"s prototype
тогда ваш цикл будет выполняться четыре раза: key
было бы 0
, 1
, 2
, а также helpfulUtilityMethod
, Если вы ожидали только целых чисел, упс.
Вы должны использовать for(var x in y)
только в списках свойств, а не в объектах (как описано выше).
С использованием for...in
Цикл для массива не является неправильным, хотя я могу догадаться, почему кто-то сказал вам, что:
1.) Уже есть функция или метод более высокого порядка, которые имеют эту цель для массива, но имеют больше функциональности и более тонкий синтаксис, называемый "forEach": Array.prototype.forEach(function(element, index, array) {} );
2.) Массивы всегда имеют длину, но for...in
а также forEach
не выполняйте функцию для любого значения, которое 'undefined'
, только для индексов, для которых определено значение. Таким образом, если вы присваиваете только одно значение, эти циклы будут выполнять функцию только один раз, но поскольку массив перечисляется, он всегда будет иметь длину до самого высокого индекса, который имеет определенное значение, но эта длина может остаться незамеченной при использовании этих значений. петли.
3.) Стандарт цикла for будет выполнять функцию столько раз, сколько вы определяете в параметрах, и, поскольку массив нумеруется, имеет смысл определить, сколько раз вы хотите выполнить функцию. В отличие от других циклов, цикл for может затем выполнять функцию для каждого индекса в массиве, независимо от того, определено это значение или нет.
По сути, вы можете использовать любой цикл, но вы должны точно помнить, как они работают. Поймите условия, при которых повторяются различные циклы, их отдельные функции, и поймите, что они будут более или менее подходящими для различных сценариев.
Кроме того, это может считаться лучшей практикой использования forEach
метод, чем for...in
Цикл в целом, потому что его легче писать и он имеет больше функциональности, поэтому вы можете захотеть использовать только этот метод и стандарт для, но ваш вызов.
Ниже показано, что первые два цикла выполняют операторы console.log только один раз, в то время как стандарт цикла выполняет функцию столько раз, сколько указано, в данном случае, array.length = 6.
var arr = [];
arr[5] = 'F';
for (var index in arr) {
console.log(index);
console.log(arr[index]);
console.log(arr)
}
// 5
// 'F'
// => (6) [undefined x 5, 6]
arr.forEach(function(element, index, arr) {
console.log(index);
console.log(element);
console.log(arr);
});
// 5
// 'F'
// => Array (6) [undefined x 5, 6]
for (var index = 0; index < arr.length; index++) {
console.log(index);
console.log(arr[index]);
console.log(arr);
};
// 0
// undefined
// => Array (6) [undefined x 5, 6]
// 1
// undefined
// => Array (6) [undefined x 5, 6]
// 2
// undefined
// => Array (6) [undefined x 5, 6]
// 3
// undefined
// => Array (6) [undefined x 5, 6]
// 4
// undefined
// => Array (6) [undefined x 5, 6]
// 5
// 'F'
// => Array (6) [undefined x 5, 6]
Цикл for...in всегда перечисляет ключи. Ключи свойств объектов всегда являются String, даже индексированные свойства массива:
var myArray = ['a', 'b', 'c', 'd'];
var total = 0
for (elem in myArray) {
total += elem
}
console.log(total); // 00123
for...in полезен при работе с объектом в JavaScript, но не для Array, но все же мы не можем сказать, что это неправильный путь, но это не рекомендуется, посмотрите на этот пример ниже, используя цикл for...in:
let txt = "";
const person = {fname:"Alireza", lname:"Dezfoolian", age:35};
for (const x in person) {
txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35
Хорошо, теперь сделаем это с Array:
let txt = "";
const person = ["Alireza", "Dezfoolian", 35];
for (const x in person) {
txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35
Как видите результат тот же...
Но давайте попробуем что-то, давайте создадим что-то для Array...
Array.prototype.someoneelse = "someoneelse";
Теперь мы создаем новый массив ();
let txt = "";
const arr = new Array();
arr[0] = 'Alireza';
arr[1] = 'Dezfoolian';
arr[2] = 35;
for(x in arr) {
txt += arr[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 someoneelse
Вы видите кого- то другого...... В этом случае мы фактически перебираем новый объект Array!
Так что это одна из причин, почему мы должны использовать для ... осторожно, но это не всегда так...
Поскольку элементы JavaScript сохраняются как стандартные свойства объекта, не рекомендуется выполнять итерацию по массивам JavaScript с использованием циклов for...in, поскольку в списке будут перечислены обычные элементы и все перечисляемые свойства.
Из https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections
Хотя этот вопрос конкретно не рассматривается, я хотел бы добавить, что есть очень веская причина не использовать его для... NodeList
(как можно было бы получить от querySelectorAll
call, так как он не видит возвращаемые элементы вообще, вместо этого перебирает только свойства NodeList.
в случае одного результата я получил:
var nodes = document.querySelectorAll(selector);
nodes
▶ NodeList [a._19eb]
for (node in nodes) {console.log(node)};
VM505:1 0
VM505:1 length
VM505:1 item
VM505:1 entries
VM505:1 forEach
VM505:1 keys
VM505:1 values
который объяснил, почему мой for (node in nodes) node.href = newLink;
терпел неудачу.
Цикл for in преобразует индексы в строку при обходе массива. Например, в приведенном ниже коде во втором цикле, где j инициализируется с помощью i+1, i является индексом, но в строке ("0", "1" и т. д.), а число + строка в js является строкой. если js встречает «0» + 1, он возвращает «01».