Frage Warum ist "for ... in" mit Array Iteration eine schlechte Idee?


Mir wurde gesagt, ich solle es nicht benutzen for...in mit Arrays in JavaScript. Warum nicht?


1539
2018-02-01 09:46


Ursprung


Antworten:


Der Grund ist, dass ein Konstrukt:

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

kann manchmal völlig anders sein als die anderen:

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

Bedenken Sie auch das JavaScript Bibliotheken können solche Dinge tun, die sich auf jedes von Ihnen erstellte Array auswirken:

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


1341
2018-02-01 10:08



Das for-in Aussage an sich ist keine "schlechte Praxis", wie auch immer sie sein mag falsch verwendetzum Beispiel zu iterieren über Arrays oder Array-ähnliche Objekte.

Der Zweck der for-in Aussage ist zu aufzählen über Objekteigenschaften. Diese Aussage wird in der Prototypkette auftauchen, auch überzählig vererbt Eigenschaften, eine Sache, die manchmal ist nicht erwünscht.

Auch die Reihenfolge der Iteration wird durch die Spezifikation nicht garantiert. Wenn Sie also ein Array-Objekt "iterieren" möchten, können Sie mit dieser Anweisung nicht sicher sein, dass die Eigenschaften (Array-Indizes) in der numerischen Reihenfolge aufgerufen werden.

Zum Beispiel wird in JScript (IE <= 8) die Reihenfolge der Aufzählung selbst bei Array-Objekten definiert, während die Eigenschaften erstellt wurden:

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
}

Sprechen Sie auch über vererbte Eigenschaften, wenn Sie beispielsweise die Array.prototype Objekt (wie einige Bibliotheken wie MooTools), werden diese Eigenschaften auch aufgelistet:

Array.prototype.last = function () { return this[this.length-1]; };

for (var p in []) { // an empty array
  // last will be enumerated
}

Wie ich schon vorher gesagt habe iterieren Über Arrays oder arrayähnliche Objekte ist das beste Ding, ein zu verwenden sequentielle Schleifewie zum Beispiel ein einfaches Alt for/while Schleife.

Wenn Sie nur die aufzählen möchten eigene Eigenschaften eines Objekts (die nicht vererbt werden), können Sie das verwenden hasOwnProperty Methode:

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

Und manche Leute empfehlen sogar, die Methode direkt aufzurufen Object.prototype um Probleme zu vermeiden, wenn jemand eine benannte Eigenschaft hinzufügt hasOwnProperty zu unserem Objekt:

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

356
2017-11-23 21:22



Es gibt drei Gründe, warum Sie nicht verwenden sollten for..in über Array-Elemente iterieren:

  • for..in Schleife über alle eigenen und geerbten Eigenschaften des Array-Objekts, die nicht sind DontEnum; das heißt, wenn jemand Eigenschaften zu dem spezifischen Array-Objekt hinzufügt (es gibt berechtigte Gründe dafür - ich habe es selbst getan) oder geändert Array.prototype (was in Code, der gut mit anderen Skripten funktionieren soll, als schlechte Praxis angesehen wird), werden diese Eigenschaften ebenfalls iteriert; Vererbte Eigenschaften können durch Überprüfung ausgeschlossen werden hasOwnProperty(), aber das hilft Ihnen nicht mit Eigenschaften, die im Array-Objekt selbst eingestellt sind

  • for..in Es ist nicht garantiert, dass die Elementordnung beibehalten wird

  • Es ist langsam, weil Sie alle Eigenschaften des Array-Objekts und seiner gesamten Prototypkette durchlaufen müssen und nur den Namen der Eigenschaft erhalten, dh um den Wert zu erhalten, ist eine zusätzliche Suche erforderlich


101
2018-02-01 14:04



Weil für ... in durch das Objekt auflistet, das das Array enthält, nicht das Array selbst. Wenn ich der Array-Prototyp-Kette eine Funktion hinzufüge, wird diese ebenfalls enthalten sein. I.e.

Array.prototype.myOwnFunction = function() { alert(this); }
a = new Array();
a[0] = 'foo';
a[1] = 'bar';
for(x in a){
 document.write(x + ' = ' + a[x]);
}

Dies wird schreiben:

0 = foo
1 = bar
myOwnFunction = function () {alert (dies); }

Und da Sie nie sicher sein können, dass der Prototypkette nichts hinzugefügt wird, verwenden Sie einfach eine for-Schleife, um das Array zu nummerieren:

for(i=0,x=a.length;i<x;i++){
 document.write(i + ' = ' + a[i]);
}

Dies wird schreiben:

0 = foo
1 = bar

48
2018-02-01 10:08



Bei der Verwendung von For-In für Arrays ist nichts falsch. For-in führt eine Iteration über die Eigenschaftsnamen eines Objekts durch, und im Falle eines "Out-of-the-Box" -Arrays entsprechen die Eigenschaften den Array-Indizes. (Die eingebauten Eigenschaften wie length, toString usw. sind nicht in der Iteration enthalten.)

Wenn Ihr Code (oder das von Ihnen verwendete Framework) jedoch benutzerdefinierte Eigenschaften zu Arrays oder zum Array-Prototyp hinzufügen, werden diese Eigenschaften in die Iteration einbezogen, was wahrscheinlich nicht Ihren Vorstellungen entspricht.

Einige JS-Frameworks, wie Prototype, modifizieren den Array-Prototyp. Andere Frameworks wie JQuery nicht, also mit JQuery können Sie for-in sicher verwenden.

Wenn Sie Zweifel haben, sollten Sie For-In wahrscheinlich nicht verwenden.

Eine alternative Möglichkeit zur Iteration durch ein Array besteht in der Verwendung einer For-Schleife:

for (var ix=0;ix<arr.length;ix++) alert(ix);

Dies hat jedoch ein anderes Problem. Das Problem ist, dass ein JavaScript-Array "Löcher" haben kann. Wenn Sie definieren arr wie:

var arr = ["hello"];
arr[100] = "goodbye";

Dann hat das Array zwei Elemente, aber eine Länge von 101. Wenn for-in verwendet wird, ergeben sich zwei Indizes, während die for-Schleife 101 Indizes ergibt, wobei das 99 einen Wert von hat undefined.


37
2018-02-01 10:34



Zusätzlich zu den in anderen Antworten angegebenen Gründen möchten Sie möglicherweise die Struktur "for ... in" nicht verwenden, wenn Sie mit der Counter-Variablen rechnen müssen, da die Schleife die Namen der Objekteigenschaften und damit die Variable durchläuft ist eine Zeichenfolge.

Beispielsweise,

for (var i=0; i<a.length; i++) {
    document.write(i + ', ' + typeof i + ', ' + i+1);
}

werde schreiben

0, number, 1
1, number, 2
...

wohingegen,

for (var ii in a) {
    document.write(i + ', ' + typeof i + ', ' + i+1);
}

werde schreiben

0, string, 01
1, string, 11
...

Natürlich kann dies leicht durch Einbeziehung überwunden werden

ii = parseInt(ii);

in der Schleife, aber die erste Struktur ist direkter.


28
2017-09-02 02:29



Ab 2016 (ES6) können wir verwenden for…of für Array-Iteration, wie John Slegers bereits bemerkt.

Ich möchte nur diesen einfachen Demonstrationscode hinzufügen, um die Dinge klarer zu machen:

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++;
    }

Die Konsole zeigt:

for...of:

0: undefined
1: undefined
2: undefined
3: undefined
4: undefined
5: xyz

for...in:

0: 5
1: foo

Mit anderen Worten:

  • for...of zählt von 0 bis 5 und ignoriert auch Array.prototype.foo. Es zeigt Array an Werte.

  • for...in listet nur die 5, undefinierte Array-Indizes ignorieren, aber hinzufügen foo. Es zeigt Array an Eigenschaftsnamen.


24
2018-03-10 04:29



Abgesehen davon, dass for...in Schleifen über alle aufzählbaren Eigenschaften (das ist nicht das gleiche wie "alle Array-Elemente"!), siehe http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf, Abschnitt 12.6.4 (5. Ausgabe) oder 13.7.5.15 (7. Ausgabe):

Die Mechanik und Auftrag die Eigenschaften aufzählen ... ist nicht angegeben...

(Hervorhebung von mir.)

Das heißt, wenn ein Browser es wollte, könnte er die Eigenschaften in der Reihenfolge durchlaufen, in der sie eingefügt wurden. Oder in numerischer Reihenfolge. Oder in lexikalischer Reihenfolge (wobei "30" vor "4" steht!) Beachten Sie, dass alle Objektschlüssel - und somit alle Array-Indizes - tatsächlich Zeichenketten sind, so dass dies insgesamt sinnvoll ist). Es könnte sie nach Bucket durchlaufen, wenn es Objekte als Hash-Tabellen implementiert. Oder nehmen Sie etwas davon und fügen Sie "rückwärts" hinzu. Ein Browser könnte sogar iterieren nach dem Zufallsprinzip und ECMA-262-konform sein, solange es jede Eigenschaft genau einmal besucht hat.

In der Praxis möchten die meisten Browser derzeit in etwa in der gleichen Reihenfolge iterieren. Aber es gibt nichts, was sie sagen müssen. Das ist implementierungsspezifisch und könnte sich jederzeit ändern, wenn sich ein anderer Weg als weitaus effizienter herausstellen würde.

In jedem Fall, for...in trägt damit keine Konnotation der Ordnung. Wenn Ihnen die Reihenfolge wichtig ist, seien Sie ausdrücklich darüber und verwenden Sie ein reguläres for Schleife mit einem Index.


21
2018-05-14 16:26



Kurze Antwort: Es ist einfach nicht wert.


Längere Antwort: Es ist einfach nicht wert, auch wenn Reihenfolge Reihenfolge und optimale Leistung nicht erforderlich sind.


Lange Antwort: Es lohnt sich einfach nicht, aus folgenden Gründen:

  • Verwenden for (var i in array) {} wird dazu führen, dass 'array' wie jeder andere interpretiert wird rein Objekt, durchqueren die Objekteigenschaftskette und schließlich langsamer als ein Index-basierte for Schleife.
  • Es ist nicht garantiert, dass die Objekteigenschaften in der sequentiellen Reihenfolge zurückgegeben werden, wie zu erwarten ist.
  • Verwenden hasOwnProperty() oder isNaN() checks, um die Objekteigenschaften zu filtern, ist ein zusätzlicher Overhead, der dazu führt, dass es (noch mehr) langsamer arbeitet. Außerdem negiert das Einführen einer solchen zusätzlichen Logik den Hauptgrund für dessen Verwendung an erster Stelle, d.h. wegen des präziseren Formats.

Aus diesen Gründen gibt es keinen akzeptablen Kompromiss zwischen Leistung und Zweckmäßigkeit. Wirklich, es gibt keinen Vorteil, es sei denn, die Absicht besteht darin, das Array als rein Objekt und führt Operationen für die Eigenschaften des Array-Objekts aus.


20
2018-03-14 07:12



Hauptsächlich zwei Gründe:

Ein

Wie andere gesagt haben, könnten Sie Schlüssel erhalten, die nicht in Ihrem Array sind oder die vom Prototyp geerbt werden. Wenn also beispielsweise eine Bibliothek den Array- oder Objektprototypen eine Eigenschaft hinzufügt:

Array.prototype.someProperty = true

Sie erhalten es als Teil jedes Arrays:

for(var item in [1,2,3]){
  console.log(item) // will log 1,2,3 but also "someProperty"
}

Sie könnten dies mit der Methode hasOwnProperty lösen:

var ary = [1,2,3];
for(var item in ary){
   if(ary.hasOwnProperty(item)){
      console.log(item) // will log only 1,2,3
   }
}

Dies gilt jedoch für das Iterieren über ein beliebiges Objekt mit einer For-In-Schleife.

Zwei

Normalerweise ist die Reihenfolge der Elemente in einem Array wichtig, aber die For-In-Schleife wird nicht notwendigerweise in der richtigen Reihenfolge durchlaufen, weil sie das Array als ein Objekt behandelt, so wie es in JS implementiert ist und nicht als ein Array. Dies scheint eine kleine Sache zu sein, aber es kann wirklich Anwendungen vermasseln und ist schwer zu debuggen.


15
2018-02-04 16:54