Почему использование "for... in" с итерацией массива - плохая идея?
Мне сказали не использовать for...in
с массивами в JavaScript. Почему бы и нет?
Ответы
Ответ 1
Причина в том, что одна конструкция:
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
*/
Ответ 2
Операция 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
}
}
Ответ 3
Существует три причины, по которым вы не должны использовать for..in
для итерации по элементам массива:
-
for..in
будет охватывать все собственные и унаследованные свойства объекта массива, которые не являются DontEnum
; это означает, что если кто-то добавляет свойства к определенному объекту массива (есть веские причины для этого - я сделал это сам) или изменил Array.prototype
(который считается плохой практикой в коде, который должен хорошо работать с другими скриптами) эти свойства также будут повторяться; унаследованные свойства можно исключить, проверив hasOwnProperty()
, но это не поможет вам со свойствами, установленными в самом объекте массива
-
for..in
не гарантируется сохранение упорядочения элементов
-
он медленный, потому что вам нужно пройти все свойства объекта массива и его целую цепочку прототипов и все равно получить только имя свойства, т.е. получить значение, потребуется дополнительный поиск
Ответ 4
Потому что для... в перечислении через объект, который содержит массив, а не сам массив. Если я добавлю функцию в цепочку прототипов массивов, это также будет включено. То есть.
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 = bar
myOwnFunction = function() { alert(this); }
И поскольку вы никогда не можете быть уверены, что в цепочку прототипов ничего не будет добавлено, просто используйте цикл for для перечисления массива:
for(i=0,x=a.length;i<x;i++){
document.write(i + ' = ' + a[i]);
}
Это будет писать:
0 = foo
1 = bar
Ответ 5
В изобилии нет ничего плохого в использовании in-in на массивах. For-in выполняет итерации над именами свойств объекта, а в случае массива "из коробки" свойства соответствуют индексам массива. (Встроенные атрибуты типа length
, toString
и т.д. Не включены в итерацию.)
Однако, если ваш код (или используемая структура) добавляет настраиваемые свойства к массивам или прототипу массива, то эти свойства будут включены в итерацию, которая, вероятно, не является тем, что вы хотите.
Некоторые JS-структуры, такие как Prototype, модифицируют прототип Array. Другие структуры, такие как JQuery, не работают, поэтому с JQuery вы можете безопасно использовать in-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-loop даст 101 индекс, где 99 имеет значение undefined
.
Ответ 6
В дополнение к причинам, приведенным в других ответах, вы можете не захотеть использовать структуру "для... в", если вам нужно выполнить математику с переменной счетчика, поскольку цикл выполняет итерации по именам свойств объекта и поэтому переменная является строкой.
Например,
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);
в цикле, но первая структура более прямая.
Ответ 7
Начиная с 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
, игнорируя индексы массива undefined, но добавляя foo
. Он показывает имена свойств массива .
Ответ 8
Краткий ответ: это просто не стоит.
Более длинный ответ: Это просто не стоит, даже если последовательный порядок элементов и оптимальная производительность не требуются.
Длинный ответ: Это просто не стоит, по следующим причинам:
- Использование
for (var property in array)
приведет к тому, что array
будет перебираться как объект, проходя цепочку прототипов объекта и в конечном итоге работая медленнее, чем основанный for
индексе цикл for
. -
for (... in...)
не гарантирует возвращение свойств объекта в последовательном порядке, как можно было бы ожидать. - Использование
hasOwnProperty()
и !isNaN()
для фильтрации свойств объекта - это дополнительная нагрузка, которая приводит к тому, что он работает (даже больше) медленнее, и сводит на нет основную причину его использования, т.е. Из-за более лаконичного формата.
По этим причинам приемлемого компромисса между производительностью и удобством даже не существует. В действительности нет никакой выгоды, если только мы не хотим обрабатывать массив как объект и выполнять операции над свойствами объекта массива.
Ответ 9
Помимо того, что for
... in
перебирает все перечислимые свойства (это не то же самое, что "все элементы массива"!), см. http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf, раздел 12.6.4 (пятое издание) или 13.7.5.15 (7-е издание):
Механика и порядок перечисления свойств... не указывается...
(Подчеркните мой.)
Это означает, что если браузер хотел, он мог бы пройти через свойства в том порядке, в котором они были вставлены. Или в численном порядке. Или в лексическом порядке (где "30" доходит до "4"! Помните о всех объектных ключах - и, следовательно, все индексы массива - фактически являются строками, поэтому это имеет смысл). Он может проходить через них ведро, если он реализует объекты как хеш-таблицы. Или возьмите любое из этого и добавьте "назад". Браузер может даже итерировать случайно и быть совместимым с ECMA-262, если он посетил каждое свойство ровно один раз.
На практике большинство браузеров в настоящее время любят итератировать примерно в том же порядке. Но ничего не говорится, что нужно. Эта реализация специфична и может измениться в любое время, если другой способ окажется более эффективным.
В любом случае for
... in
не несет никакой коннотации порядка. Если вы заботитесь о порядке, будьте откровенны и используйте обычный цикл for
с индексом.
Ответ 10
В основном две причины:
One
Как и другие, вы можете получить ключи, которые не находятся в вашем массиве или унаследованы от прототипа. Итак, если, скажем, библиотека добавляет свойство к прототипам 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, а не как массив.
Это похоже на небольшую вещь, но это может действительно испортить приложения и трудно отлаживать.
Ответ 11
Потому что он перечисляет через поля объекта, а не индексы. Вы можете получить значение с индексом "длина", и я сомневаюсь, что вы этого хотите.
Ответ 12
Проблема с for ... in ...
— и это становится проблемой, когда программист действительно не понимает язык; это не ошибка или что-то еще; заключается в том, что он выполняет итерацию по всем элементам объекта (ну, все перечисляемые члены, но эта деталь на данный момент). Если вы хотите перебирать только индексированные свойства массива, единственным гарантированным способом сохранения семантически согласованных значений является использование целочисленного индекса (т.е. Цикла стиля for (var i = 0; i < array.length; ++i)
).
Любой объект может иметь связанные с ним произвольные свойства. Не было бы ничего страшного в загрузке дополнительных свойств в экземпляр массива, в частности. Код, который хочет видеть только индексированные свойства, подобные массиву, поэтому должен придерживаться целочисленного индекса. Код, который полностью знает, что for ... in
действительно и действительно должен видеть все свойства, ну, тогда это тоже нормально.
Ответ 13
Я не думаю, что мне есть что добавить, например. Триптих-ответ или ответ CMS о том, почему в некоторых случаях следует избегать использования for...in
.
Однако я хотел бы добавить, что в современных браузерах есть альтернатива for...in
которая может использоваться в тех случаях, когда for...in
не может использоваться. Эта альтернатива for...of
:
for (var item of items) {
console.log(item);
}
Замечания:
К сожалению, ни одна из версий Internet Explorer не поддерживает for...of
(Edge 12+), поэтому вам придется подождать немного дольше, пока вы не сможете использовать ее в рабочем коде на стороне клиента. Тем не менее, он должен быть безопасным для использования в вашем JS-коде на стороне сервера (если вы используете Node.js).
Ответ 14
Кроме того, из-за семантики путь 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"
Ответ 15
TL & DR: Использование цикла for in
в массивах не является злом, на самом деле все наоборот.
Я думаю, что цикл for in
является жемчужиной 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]);
Ответ 16
for
/in
работает с двумя типами переменных: hashtables (ассоциативные массивы) и массив (неассоциативный).
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]);
Ответ 17
В дополнение к другим проблемам синтаксис "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'
Ответ 18
Важным аспектом является то, что for...in
выполняет только итерации над свойствами, содержащимися в объекте, которые имеют атрибут свойства перечисляемый, установленный в true. Поэтому, если вы пытаетесь выполнить итерацию объекта с помощью for...in
, тогда любые свойства могут быть пропущены, если их атрибут enumerable property равен false. Вполне возможно изменить атрибут enumerable property для обычных объектов Array, чтобы определенные элементы не были перечислены. Хотя, как правило, атрибуты свойств имеют тенденцию относиться к свойствам функции внутри объекта.
Можно проверить значение атрибута свойства enumerable свойства:
myobject.propertyIsEnumerable('myproperty')
Или получить все четыре атрибута свойства:
Object.getOwnPropertyDescriptor(myobject,'myproperty')
Это функция, доступная в ECMAScript 5 - в более ранних версиях было невозможно изменить значение атрибута enumerable property (он всегда был равен true).
Ответ 19
Потому что он будет перебирать свойства, принадлежащие объектам, в цепочке прототипов, если вы не будете осторожны.
Вы можете использовать for.. in
, просто обязательно проверьте каждое свойство с помощью hasOwnProperty.
Ответ 20
Это не обязательно плохо (на основе того, что вы делаете), но в случае массивов, если что-то добавлено в Array.prototype
, тогда вы получите странные результаты. Где вы ожидаете, что этот цикл будет выполняться три раза:
var arr = ['a','b','c'];
for (var key in arr) { ... }
Если в Array
prototype
добавлена функция с именем helpfulUtilityMethod
, тогда ваш цикл будет работать четыре раза: key
будет 0
, 1
, 2
и helpfulUtilityMethod
. Если вы ожидали только целых чисел, oops.
Ответ 21
Вы должны использовать for(var x in y)
только в списках свойств, а не на объектах (как описано выше).
Ответ 22
Использование цикла for...in
для массива не является неправильным, хотя я могу догадаться, почему кто-то сказал вам, что:
1.) Существует уже функция или метод более высокого порядка, который имеет эту цель для массива, но имеет больше функциональности и более компактный синтаксис, называемый "forEach": Array.prototype.forEach(function(element, index, array) {} );
2.) Массивы всегда имеют длину, но for...in
и forEach
не выполняют функцию для любого значения 'undefined'
, только для индексов, которые имеют определенное значение. Поэтому, если вы назначаете только одно значение, эти циклы будут выполнять только один раз один раз, но поскольку перечисляется массив, он всегда будет иметь длину до наивысшего индекса, имеющего определенное значение, но эта длина может остаться незамеченной при использовании этих петли.
3.) Стандарт для цикла будет выполнять функцию столько раз, сколько вы определяете в параметрах, и поскольку массив пронумерован, имеет смысл определить, сколько раз вы хотите выполнить функцию. В отличие от других циклов, цикл for может затем выполнять функцию для каждого индекса в массиве, независимо от того, определено это значение или нет.
В сущности, вы можете использовать любой цикл, но вы должны точно помнить, как они работают. Понимать условия, на которых повторяются разные циклы, их отдельные функции и понимать, что они будут более или менее подходящими для разных сценариев.
Кроме того, лучше всего использовать метод forEach
, чем цикл for...in
в целом, поскольку его легче писать и имеет больше функциональных возможностей, поэтому вы можете захотеть привыкнуть только к используя этот метод и стандарт для, но ваш вызов.
См. ниже, что первые две петли выполняют только операторы console.log один раз, в то время как стандарт for loop выполняет функцию столько раз, сколько указано, в этом случае 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]
Ответ 23
для... в полезен при работе над объектом в JavaScript, но не для массива, но мы не можем сказать это неправильно, но это не рекомендуется, посмотрите на это пример ниже, используя цикл для... в:
let txt = "";
const person = {fname:"Alireza", lname:"Dezfoolian", age:35};
for (const x in person) {
txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35
ОК, сделайте это с помощью Массив:
let txt = "";
const person = ["Alireza", "Dezfoolian", 35];
for (const x in person) {
txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35
Как вы видите результат тот же самый...
Но попробуй что-нибудь, пусть прототип чего-то Массив...
Array.prototype.someoneelse = "someoneelse";
Теперь мы создаем новый массив Array();
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
Вы видите someelse!!!... Мы в этом случае перебираем новый объект Array!
Итак, одна из причин, по которой нам нужно использовать для... внутри, но это не всегда так...
Ответ 24
A for... in loop всегда перечисляет ключи. Ключами свойств объектов всегда являются String, даже индексированные свойства массива:
var myArray = ['a', 'b', 'c', 'd'];
var total = 0
for (elem in myArray) {
total += elem
}
console.log(total); // 00123
Ответ 25
Так как элементы JavaScript сохраняются как стандартные свойства объекта, это не рекомендуется выполнять итерацию с помощью массивов JavaScript, используя для... в поскольку обычные элементы и все перечислимые свойства будут в списке.
От https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections
Ответ 26
хотя конкретно и не затрагивался этот вопрос, я хотел бы добавить, что есть очень хорошая причина, чтобы не когда - либо использовать для... в с NodeList
(как можно было бы получить от querySelectorAll
вызова, так как он не видит возвращаемые элементы на всех вместо этого перебирая только свойства 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;
терпел неудачу.
Ответ 27
Вот причины, по которым это (обычно) плохая практика:
- Циклы
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);
}