Мне сказали не использовать for...in
с массивами в JavaScript. Почему бы и нет?
Причина в том, что одна конструкция:
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
*/
(var x in a)
вместо (x in a)
- не хотите создавать глобальный.
Операция 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
Дэвида Хамфри. Итерации по объектам в JavaScript быстро - для массива for..in
намного медленнее, чем «нормальные» циклы.
Существует три причины, по которым вы не должны использовать for..in
для итерации по элементам массива:
for..in
будет охватывать все собственные и унаследованные свойства объекта массива, которые не являются DontEnum
; это означает, что если кто-то добавляет свойства к определенному объекту массива (есть веские причины для этого - я сделал это сам) или изменил Array.prototype
(который считается плохой практикой в коде, который должен хорошо работать с другими скриптами) эти свойства также будут повторяться; унаследованные свойства можно исключить, проверив hasOwnProperty()
, но это не поможет вам со свойствами, установленными в самом объекте массива
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 = bar myOwnFunction = function() { alert(this); }
И поскольку вы никогда не можете быть уверены, что в цепочку прототипов ничего не будет добавлено, просто используйте цикл for для перечисления массива:
for(i=0,x=a.length;i<x;i++){
document.write(i + ' = ' + a[i]);
}
Это будет писать:
0 = foo 1 = bar
В изобилии нет ничего плохого в использовании 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
.
В дополнение к причинам, приведенным в других ответах, вы можете не захотеть использовать структуру "для... в", если вам нужно выполнить математику с переменной счетчика, поскольку цикл выполняет итерации по именам свойств объекта и поэтому переменная является строкой.
Например,
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);
в цикле, но первая структура более прямая.
+
вместо parseInt
если вам действительно не нужно целое число или игнорировать недопустимые символы.
parseInt()
не рекомендуется. Попробуйте parseInt("025");
и это не удастся.
Начиная с 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
. Он показывает имена свойств массива .
Короткий ответ: это просто не стоит.
Более длинный ответ: это просто не стоит, даже если порядок последовательных элементов и оптимальная производительность не требуются.
Длинный ответ: это просто не стоит, по следующим причинам:
for (var i in array) {}
приведет к тому, что 'array' будет интерпретироваться как любой другой чистый объект, пройдя цепочку свойств объекта и, в конечном счете, выполняя медленнее, чем цикл for
на основе индексов.hasOwnProperty()
или isNaN()
проверок для фильтрации свойств объекта - дополнительные накладные расходы, заставляющие его выполнять (даже больше) медленнее. Кроме того, введение такой дополнительной логики отрицает основную причину ее использования в первую очередь, то есть из-за более сжатого формата.По этим причинам приемлемый компромисс между производительностью и удобством даже не существует. Действительно, нет никакой выгоды, если только намерение заключается в том, чтобы рассматривать массив как чистый объект и выполняет операции над свойствами объекта массива.
Помимо того, что 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
с индексом.
В основном две причины:
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, а не как массив. Это похоже на небольшую вещь, но это может действительно испортить приложения и трудно отлаживать.
Object.keys(a).forEach( function(item) { console.log(item) } )
перебирает массив собственных ключей свойств, а не тех, которые унаследованы от прототипа.
Потому что он перечисляет через поля объекта, а не индексы. Вы можете получить значение с индексом "длина", и я сомневаюсь, что вы этого хотите.
Проблема с for ... in ...
— и это становится проблемой, когда программист действительно не понимает язык; это не ошибка или что-то еще; заключается в том, что он выполняет итерацию по всем элементам объекта (ну, все перечисляемые члены, но эта деталь на данный момент). Если вы хотите перебирать только индексированные свойства массива, единственным гарантированным способом сохранения семантически согласованных значений является использование целочисленного индекса (т.е. Цикла стиля for (var i = 0; i < array.length; ++i)
).
Любой объект может иметь связанные с ним произвольные свойства. Не было бы ничего страшного в загрузке дополнительных свойств в экземпляр массива, в частности. Код, который хочет видеть только индексированные свойства, подобные массиву, поэтому должен придерживаться целочисленного индекса. Код, который полностью знает, что for ... in
действительно и действительно должен видеть все свойства, ну, тогда это тоже нормально.
for in
, по сравнению с обычным циклом for, эти массивы перебирались бы? (что по сути, медленная производительность, верно?)
in
в for ... in
цикле будет просто
Я не думаю, что мне есть что добавить. Ответ на триптих или ответ 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
- это оператор ES6 (AKA ECMAScript 2015), который выполняет итерации по данным, которые итерируемые объекты определяют для итерации. Оператор for...in
перебирает перечисляемые свойства объекта в произвольном порядке.
Кроме того, из-за семантики путь 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"
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]);
Итак, вы видите, что цикл 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
затмевает другие «если», то массив редок, и чем больше, тем больше его размер. Поэтому я переставил стендовый тест для разреженного массива arr
, размером ~ 10000, только с 50 случайно выбранными элементами из [42,"test",{t:1},null, void 0]
со случайными индексами. Вы сразу заметите разницу. - >> Проверьте это здесь << - .
Важным аспектом является то, что for...in
выполняет только итерации над свойствами, содержащимися в объекте, которые имеют атрибут свойства перечисляемый, установленный в true. Поэтому, если вы пытаетесь выполнить итерацию объекта с помощью for...in
, тогда любые свойства могут быть пропущены, если их атрибут enumerable property равен false. Вполне возможно изменить атрибут enumerable property для обычных объектов Array, чтобы определенные элементы не были перечислены. Хотя, как правило, атрибуты свойств имеют тенденцию относиться к свойствам функции внутри объекта.
Можно проверить значение атрибута свойства enumerable свойства:
myobject.propertyIsEnumerable('myproperty')
Или получить все четыре атрибута свойства:
Object.getOwnPropertyDescriptor(myobject,'myproperty')
Это функция, доступная в ECMAScript 5 - в более ранних версиях было невозможно изменить значение атрибута enumerable property (он всегда был равен true).
В дополнение к другим проблемам синтаксис "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'
var i in a
и ожидайте, что индекс будет целочисленным, тогда выполнение чего-то вроде a[i+offset] = <value>
будет помещать значения в совершенно неправильные места , («1» + 1 == «11»).
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]);
Array
тоже Object
Потому что он будет перебирать свойства, принадлежащие объектам, в цепочке прототипов, если вы не будете осторожны.
Вы можете использовать for.. in
, просто обязательно проверьте каждое свойство с помощью hasOwnProperty.
true
из hasOwnProperty()
.
Вы должны использовать for(var x in y)
только в списках свойств, а не на объектах (как описано выше).
Это не обязательно плохо (на основе того, что вы делаете), но в случае массивов, если что-то добавлено в Array.prototype
, тогда вы получите странные результаты. Где вы ожидаете, что этот цикл будет выполняться три раза:
var arr = ['a','b','c'];
for (var key in arr) { ... }
Если в Array
prototype
добавлена функция с именем helpfulUtilityMethod
, тогда ваш цикл будет работать четыре раза: key
будет 0
, 1
, 2
и helpfulUtilityMethod
. Если вы ожидали только целых чисел, oops.
Использование цикла 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]
A for... in loop всегда перечисляет ключи. Ключами свойств объектов всегда являются String, даже индексированные свойства массива:
var myArray = ['a', 'b', 'c', 'd'];
var total = 0
for (elem in myArray) {
total += elem
}
console.log(total); // 00123
для... в полезен при работе над объектом в 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!
Итак, одна из причин, по которой нам нужно использовать для... внутри, но это не всегда так...
Так как элементы JavaScript сохраняются как стандартные свойства объекта, это не рекомендуется выполнять итерацию с помощью массивов JavaScript, используя для... в поскольку обычные элементы и все перечислимые свойства будут в списке.
От https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections