web-dev-qa-db-ja.com

なぜ配列の繰り返しで "for ... in"を使うのは悪い考えなのでしょうか。

私はJavaScriptの配列でfor...inを使わないように言われました。何故なの?

1669
lYriCAlsSH

その理由は、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
*/

他のものとはまったく異なる場合があります。

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 librariesがこのようなことをするかもしれないと考えてください。

// 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
*/

1427
Triptych

for-inステートメント自体は「悪い習慣」ではありませんが、例えば、配列や配列のようなオブジェクトに対してiterateのように誤用になることがあります。

for-inステートメントの目的は、オブジェクトプロパティを列挙することです。この文はプロトタイプチェーンの中で、inheritedプロパティについても列挙していますが、これはたまには望ましくないということです。

また、反復の順序は仕様によって保証されていません。つまり、この文を使用して配列オブジェクトを "反復"したい場合、プロパティ(配列インデックス)が数値順に表示されることは保証できません。

たとえば、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
}

前にも言ったように、配列や配列のようなオブジェクトに対してiterateをするとき、一番良いのは普通のfor/whileループのようなsequential loopを使うことです。

オブジェクトの独自のプロパティ(継承されていないもの)のみを列挙する場合は、hasOwnPropertyメソッドを使用できます。

for (var prop in obj) {
  if (obj.hasOwnProperty(prop)) {
    // prop is not inherited
  }
}

誰かが私たちのオブジェクトにhasOwnPropertyという名前のプロパティを追加した場合に問題を回避するためにObject.prototypeから直接メソッドを呼び出すことを勧めさえします:

for (var prop in obj) {
  if (Object.prototype.hasOwnProperty.call(obj, prop)) {
    // prop is not inherited
  }
}
364
CMS

配列要素を反復処理するのにfor..inを使用してはいけない理由は3つあります。

  • for..inは、DontEnumではない配列オブジェクトのすべての自身の継承されたプロパティをループします。つまり、誰かが特定の配列オブジェクトにプロパティを追加した場合(これには正当な理由があります - 私は自分自身で行ったことがあります)またはArray.prototypeを変更します。同様に繰り返されます。継承されたプロパティはhasOwnProperty()をチェックすることで除外することができますが、それは配列オブジェクト自体に設定されたプロパティを使うのに役立ちません

  • for..inは要素の順序を維持することを保証されていません

  • 配列オブジェクトとそのプロトタイプチェーン全体のすべてのプロパティを調べなければならず、プロパティの名前しか取得できないため、処理が遅くなります。つまり、値を取得するには、追加の検索が必要になります。

107
Christoph

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 
50
Pim Jager

単独では、配列にfor-inを使用しても問題はありません。 for-inはオブジェクトのプロパティ名を繰り返し処理し、 "out-of-the-box"配列の場合、プロパティは配列インデックスに対応します。 (lengthtoStringなどの組み込みプロパティは繰り返しに含まれません。)

ただし、コード(または使用しているフレームワーク)がカスタムプロパティを配列または配列プロトタイプに追加すると、これらのプロパティは繰り返しに含まれることになります。

Prototypeなどの一部のJSフレームワークは、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";

その場合、配列には2つの項目がありますが、長さは101です。for-inを使用すると2つのインデックスが生成され、forループでは101のインデックスが生成されます。99の値はundefinedです。

38
JacquesB

他の答えで与えられた理由に加えて、ループがオブジェクトのプロパティの名前を通して繰り返されるので、counter変数を使って数学をする必要があるならば、 "for ... in"構造を使いたくないでしょう文字列です。

例えば、

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);

ループ内ですが、最初の構造がより直接的です。

30
ctmiddle

John Slegersがすでに気づいているように、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...in5のみをリストし、未定義の配列インデックスは無視しますが、fooを追加します。配列 プロパティ名 が表示されます。

29
MarcG

for...inがすべての列挙可能なプロパティをループするという事実は別として(これは、「すべての配列要素」と同じです(not _と同じ!)、 http://www.ecma-international.org /publications/files/ECMA-ST/Ecma-262.pdf 、セクション12.6.4(第5版)または13.7.5.15(第7版):

プロパティを列挙する仕組みと order ... が指定されていない ...

(私の強調)

つまり、ブラウザが望む場合は、挿入された順序でプロパティを通過することができます。または番号順に。または字句順( "30"が "4"の前にくる!すべてのオブジェクトキー、つまりすべての配列インデックス)は実際には文字列であるため、完全に意味があります。オブジェクトをハッシュテーブルとして実装している場合、バケツでそれらを通過する可能性があります。またはそれのいずれかを取り、 "後方"を追加してください。ブラウザは、各プロパティを一度だけ訪れた限り、randomlyを繰り返し、ECMA-262に準拠することさえできます。

実際には、ほとんどのブラウザは現在、ほぼ同じ順序で反復することを好みます。しかし、彼らがしなければならないと言っているものは何もありません。これは実装固有のもので、他の方法がはるかに効率的であることが判明した場合はいつでも変更できます。

どちらにしても、for...inは順序を暗示するものではありません。順序が気になる場合は、それについて明確にし、索引付きの通常のforループを使用してください。

22
cHao

短い答え:それは価値がないだけです。


長い答え:連続した要素の順序と最適なパフォーマンスが要求されなくても、それは価値がないだけです。


長い答え:次のような理由から、それだけの価値はありません。

  • for (var i in array) {}を使用すると、 'array'は他の pure objectとして解釈され、オブジェクトのプロパティチェーンを移動し、最終的にインデックスベースのforループよりも遅くなります。
  • 予想されるように、オブジェクトプロパティを順番に返すことは保証されていません。
  • hasOwnProperty()またはisNaN()チェックを使用してオブジェクトのプロパティをフィルタ処理すると、オーバーヘッドが増し、パフォーマンスが遅くなります(さらに遅くなります)。また、そのような追加の論理を導入することは、そもそもそれを使用する主な理由、すなわちより簡潔なフォーマットのためにそれを否定する。

これらの理由から、パフォーマンスと利便性の間の許容できるトレードオフは存在しません。実際には、配列を pure オブジェクトとして扱い、配列オブジェクトのプロパティに対して操作を実行することを目的としていない限り、利点はありません。

21
WynandB

インデックスではなくオブジェクトフィールドを介して列挙するためです。あなたはインデックス "length"で値を得ることができますそして私はあなたがこれを望んでいるとは思わない。

15
vava

主に2つの理由

1

他の人が言っているように、あなたはあなたの配列にないか、プロトタイプから継承されるキーを得るかもしれません。たとえば、ライブラリが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では実装されている方法であり、 これは小さなことのように思えますが、実際にはアプリケーションを台無しにしている可能性があり、デバッグするのは困難です。

15
Lior

for ... in ...の問題 - そしてこれはプログラマーがその言語を本当に理解していないときだけ問題になります。それは実際にはバグではありません - オブジェクトのallメンバー(繰り返しますが、すべてのenumerableメンバー)を繰り返し処理するということです。配列の添字付きプロパティについてjustを反復処理したい場合、意味的に一貫性を保つための唯一の保証された方法は整数添字(つまりfor (var i = 0; i < array.length; ++i)スタイルのループ)を使うことです。

任意のオブジェクトはそれに関連付けられた任意のプロパティを持つことができます。特に、追加のプロパティを配列インスタンスにロードすることに関してそれほど恐ろしいことは何もありません。したがって、only添字付き配列様プロパティを見たいコードは、must整数型インデックスに固執します。 for ... inが何をするのかを完全に認識していて、すべてのプロパティを見るために実際にはneedを使っているコードです。

14
Pointy

私が追加することがたくさんあるとは思わない。 Triptychの回答 または CMSの回答for-inの使用を回避する必要がある理由.

ただし、 を追加したいのですが、最近のブラウザでは for-inを使用できない場合に使用できるfor-inの代替手段があります。その代替案は for-of :です。

for (var item of items) {
    console.log(item);
}

注意 :

残念ながら、Internet Explorerのどのバージョンでもこの機能をサポートしていません( Edge 12+ do)ので、クライアントサイドのプロダクションコードで使用できるようになるまで少し時間がかかります。ただし、サーバーサイドのJSコードで安全に使用する必要があります( Node.js を使用する場合)。

10
John Slegers

TL&DR:配列でfor inループを使用するのは悪くありません。実際はまったく逆です。

for inループは、配列で正しく使用されている場合、JSの宝石だと思います。ソフトウェアを完全に制御し、自分が何をしているかを知っていることが期待されます。上記の欠点を見て、それらを1つずつ反証しましょう。

  1. 継承されたプロパティもループします:まず、Array.prototypeの拡張は、 Object.defineProperty() を使用して行われるべきであり、そのenumerable記述子はfalseに設定します。そうしないライブラリは使用しないでください。
  2. 後で継承チェーンに追加するプロパティはカウントされます:Object.setPrototypeOfまたはクラスextendによる配列のサブクラス化を行う場合。再びObject.defineProperty()を使用する必要があります。これは、デフォルトでwritableenumerable、および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ループは安全です。

  1. for inループが遅い:時々必要となる疎な配列をループする場合、これは間違いなく最速の反復方法です。これは、知っておくべき最も重要なパフォーマンストリックの1つです。例を見てみましょう。スパース配列をループします。
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:");
9
Redu

また、セマンティクスのため、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"
8
matpop

他の問題に加えて、インデックスは整数ではなく文字列なので、 "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'
7
dc1

重要な点は、for...inは、 enumerable property属性 がtrueに設定されているオブジェクトに含まれるプロパティに対してのみ反復するということです。そのため、for...inを使用してオブジェクトを反復しようとすると、それらのenumerableプロパティ属性がfalseの場合、任意のプロパティが見逃される可能性があります。通常のArrayオブジェクトのenumerableプロパティ属性を変更して、特定の要素が列挙されないようにすることはかなり可能です。一般にプロパティ属性はオブジェクト内の関数プロパティに適用される傾向があります。

プロパティの列挙可能なプロパティ属性の値は、次の方法で確認できます。

myobject.propertyIsEnumerable('myproperty')

4つのプロパティ属性すべてを取得するには

Object.getOwnPropertyDescriptor(myobject,'myproperty')

これはECMAScript 5で利用可能な機能です - 以前のバージョンではenumerable property属性の値を変更することは不可能でした(それは常にtrueに設定されていました)。

7
Pierz

for/inは、ハッシュテーブル(連想配列)と配列(非連想配列)の2種類の変数で機能します。

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]);
7
Ricardo

注意しないと、プロトタイプチェーンの上位にあるオブジェクトに属するプロパティを反復処理するためです。

あなたはfor.. inを使うことができます、ちょうど hasOwnProperty で各プロパティをチェックするようにしてください.

6
JAL

それは必然的に悪いことではありません(あなたがしていることに基づいています)が、配列の場合、何かがArray.prototypeに追加されると、奇妙な結果が得られます。このループが3回実行されると予想される場所:

var arr = ['a','b','c'];
for (var key in arr) { ... }

helpfulUtilityMethodという関数がArrayprototypeに追加されている場合、ループは4回実行されることになります。key012、およびhelpfulUtilityMethod。整数しか期待していないなら、おっと。

6
josh3736

for(var x in y)はプロパティリストでのみ使用し、オブジェクトでは使用しないでください(上記のとおり)。

5
user268396

配列にfor...inループを使用しても問題ありませんが、なぜ誰かがあなたに言ったのか私は推測できます:

1.)配列のための目的を持っていますが、 'forEach'と呼ばれる、より多くの機能とより簡単なシンタックスを持っている、より高次の関数またはメソッドがすでにあります:Array.prototype.forEach(function(element, index, array) {} );

2.)配列は常に長さを持ちますが、for...inforEachは、'undefined'の値に対しては関数を実行せず、値が定義されているインデックスに対してのみ実行します。したがって、値を1つだけ割り当てた場合、これらのループは関数を1回だけ実行しますが、配列が列挙されるため、定義済みの値を持つ最大のインデックスまでの長さが常に存在します。ループします。

3.)forループの標準では、パラメータで定義した回数だけ関数が実行されます。また、配列には番号が付けられているため、関数を実行する回数を定義した方が理にかなっています。他のループとは異なり、forループは値が定義されているかどうかにかかわらず、配列内のすべてのインデックスに対して関数を実行できます。

本質的には、任意のループを使用できますが、それらがどのように機能するかを正確に覚えておくべきです。さまざまなループが繰り返される条件、それらの個別の機能を理解し、それらがさまざまなシナリオに適していることを理解します。 

また、一般的にfor...inループよりもforEachメソッドを使用するほうが良い方法と考えられます。これは、記述が簡単で機能が多いためです。しかしあなたの電話。

最初の2つのループはconsole.logステートメントを1回しか実行しませんが、標準のforループは指定された回数だけ関数を実行します(この場合は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]
3
mrmaclean89

For ... inループは常にキーを列挙します。オブジェクトのプロパティキーは、配列のインデックス付きプロパティであっても常にStringです。

var myArray = ['a', 'b', 'c', 'd'];
var total = 0
for (elem in myArray) {
  total += elem
}
console.log(total); // 00123
2
Maher Tliba

これが(通常)悪い習慣である理由はここにあります:

  1. 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);
}
  1. for...inループ特定の反復順序を保証しない。最近のほとんどのブラウザでは順序が通常見られますが、100%の保証はまだありません。
  2. 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]);
}
2

for ... inは、JavaScriptではオブジェクトを操作するときに便利ですが、配列には役立ちませんが、それでも間違った方法であるとは言えません。for ... in loop:

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";

今度は新しい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

someoneelse !!!が表示されます。この場合、実際には新しいArrayオブジェクトをループ処理しています。

ですから、これがfor..inを慎重に使用する必要がある理由の1つですが、必ずしもそうとは限りません...

1
Alireza

JavaScriptの要素は標準のオブジェクトプロパティとして保存されるので、通常の要素と列挙可能なすべてのプロパティはになるため、ループでfor ...を使用してJavaScript配列を繰り返すことはお勧めできません。記載されています。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections から 

0
Badr Elmers

この質問では特に触れませんでしたが、NodeListを使って... inを使ってはいけないという非常に良い理由があることを付け加えます(返された要素がまったく表示されないため、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;が失敗した理由を説明しました。

0
jcomeau_ictx