Dlaczego używanie „for… in” z iteracją tablicy jest złym pomysłem?


1823

Powiedziano mi, żebym nie korzystał for...inz tablic w JavaScript. Dlaczego nie?


45
Widziałem ostatnie pytanie, w którym ktoś ci to powiedział, ale chodziło tylko o tablice. Uważa się, że jest to zła praktyka w przypadku iteracji przez tablice, ale niekoniecznie w przypadku iteracji przez elementy obiektu.
mmurch

19
Wiele odpowiedzi zawierających pętle „for”, takie jak „for (var i = 0; i <hColl.length; i ++) {}” w porównaniu do „var i = hColl.length; natomiast (i--) {} ', który, gdy można użyć tej drugiej formy, jest znacznie szybszy. Wiem, że to styczne, ale pomyślałem, że mógłbym to dodać.
Mark Schultheiss,

2
@MarkSchultheiss, ale to odwrotna iteracja. Czy istnieje inna wersja iteracji do przodu, która jest szybsza?
ma11hew28,

5
@Wynand używa var i = hCol1.length; for (i;i;i--;) {}pamięci podręcznej i, ponieważ będzie to miało znaczenie i uprości test. - im starsza przeglądarka, tym większa różnica między fori whileZAWSZE buforuje licznik „i” - i oczywiście negatywne nie zawsze pasuje do sytuacji, a negatywne, podczas obfuscate gdy kod trochę dla niektórych osób. i zwróć uwagę, var i = 1000; for (i; i; i--) {}a var b =1000 for (b; b--;) {}gdzie i idzie od 1000 do 1, a b od 999 do 0. - im starsza przeglądarka, tym bardziej sprzyja wydajności.
Mark Schultheiss

9
Możesz także być sprytny. for(var i = 0, l = myArray.length; i < l; ++i) ...jest najszybszym i najlepszym, jakie można uzyskać dzięki iteracji do przodu.
Mathieu Amiot

Odpowiedzi:


1557

Powodem jest to, że jeden 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
*/

czasami może się zupełnie różnić od innych:

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

Weź również pod uwagę, że biblioteki JavaScript mogą robić takie rzeczy, co wpłynie na każdą utworzoną tablicę:

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


146
Historycznie niektóre przeglądarki powtarzały nawet „długość”, „toString” itd.!
bobince

398
Pamiętaj, aby używać (var x in a)zamiast (x in a)- nie chcę tworzyć globalnego.
Chris Morgan,

78
Pierwszy problem nie jest powodem, dla którego jest zły, tylko różnica w semantyce. Drugi problem wydaje mi się powodem (poza konfliktami między bibliotekami, które robią to samo), że zmiana prototypu wbudowanego typu danych jest zła, a nie zła dla… in.
Stewart

86
@Stewart: Wszystkie obiekty w JS są asocjacyjne. Tablica JS jest obiektem, więc tak, jest również skojarzona, ale nie po to jest. Jeśli chcesz iterować klucze obiektu , użyj for (var key in object). Jeśli jednak chcesz iterować elementy tablicy , użyj for(var i = 0; i < array.length; i += 1).
Martijn

42
W pierwszym przykładzie powiedziałeś, że iteruje ponad indeksy numeryczne od 0 do 4, jak wszyscy się spodziewają , oczekuję, że iteruje od 0 do 5 ! Ponieważ jeśli dodasz element w pozycji 5, tablica będzie miała 6 elementów (5 z nich jest niezdefiniowanych).
stivlo

393

Samo for-instwierdzenie nie jest „złą praktyką”, jednak może być niewłaściwie użyte , na przykład do iteracji po tablicach lub obiektach podobnych do tablic.

Celem for-ininstrukcji jest wyliczenie właściwości obiektu. To oświadczenie wejdzie w górę w łańcuchu prototypów, wyliczając również odziedziczone właściwości, co czasami nie jest pożądane.

Ponadto specyfikacja nie gwarantuje kolejności iteracji, co oznacza, że ​​jeśli chcesz „iterować” obiekt tablicowy, dzięki tej instrukcji nie możesz być pewien, że właściwości (indeksy tablicowe) będą odwiedzane w kolejności numerycznej.

Na przykład w JScript (IE <= 8) kolejność wyliczania nawet w obiektach Array jest definiowana w miarę tworzenia właściwości:

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
}

Mówiąc również o odziedziczonych właściwościach, jeśli na przykład rozszerzysz Array.prototypeobiekt (jak niektóre biblioteki, jak robią to MooTools), właściwości te zostaną również wyliczone:

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

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

Jak powiedziałem wcześniej, aby iterować po tablicach lub obiektach podobnych do tablic, najlepszą rzeczą jest użycie pętli sekwencyjnej , takiej jak zwykła for/ whilepętla.

Jeśli chcesz wyliczyć tylko własne właściwości obiektu (te, które nie są dziedziczone), możesz użyć hasOwnPropertymetody:

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

Niektórzy nawet zalecają bezpośrednie wywołanie metody, Object.prototypeaby uniknąć problemów, jeśli ktoś doda właściwość o nazwie hasOwnPropertydo naszego obiektu:

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

10
Zobacz także post Davida Humphreya Iterowanie po obiektach w JavaScript Szybko - tablica for..injest znacznie wolniejsza niż „normalne” pętle.
Chris Morgan

17
Pytanie o ostatni punkt na temat „hasOwnProperty”: Jeśli ktoś zastąpi „hasOwnProperty” na obiekcie, będziesz mieć problemy. Ale czy nie będziesz mieć takich samych problemów, jeśli ktoś zastąpi „Object.prototype.hasOwnProperty”? Tak czy inaczej to cię psują i to nie twoja odpowiedzialność, prawda?
Scott Rippey,

Mówisz, że for..innie jest to zła praktyka, ale może być nadużywana. Czy masz przykład dobrej praktyki z prawdziwego świata, w którym naprawdę chciałeś przejrzeć wszystkie właściwości obiektów, w tym właściwości odziedziczone?
rjmunro

4
@ScottRippey: Jeśli chcesz go tam zabrać: youtube.com/watch?v=FrFUI591WhI
Nathan Wall

z tą odpowiedzią stwierdziłem, że można uzyskać dostęp do wartości za pomocąfor (var p in array) { array[p]; }
equiman

117

Istnieją trzy powody, dla których nie powinieneś for..initerować elementów tablicy:

  • for..inzapętli wszystkie własne i odziedziczone właściwości obiektu tablicy, które nie są DontEnum; oznacza to, że jeśli ktoś doda właściwości do konkretnego obiektu tablicy (istnieją uzasadnione powody - sam to zrobiłem) lub zmieniłem Array.prototype(co jest uważane za złą praktykę w kodzie, który powinien działać dobrze z innymi skryptami), właściwości te będą powtarzać się również; odziedziczone właściwości można wykluczyć poprzez sprawdzenie hasOwnProperty(), ale to nie pomoże ci z właściwościami ustawionymi w samym obiekcie tablicy

  • for..in nie gwarantuje się zachowania kolejności elementów

  • jest powolny, ponieważ musisz przejść wszystkie właściwości obiektu tablicy i całego jego łańcucha prototypów i nadal otrzyma tylko nazwę właściwości, tj. aby uzyskać wartość, konieczne będzie dodatkowe wyszukiwanie


55

Ponieważ dla ... w wylicza przez obiekt, który zawiera tablicę, a nie samą tablicę. Jeśli dodam funkcję do łańcucha prototypów tablic, zostanie to również uwzględnione. To znaczy

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

To napisze:

0 = foo
1 = słupek
myOwnFunction = function () {alert (this); }

A ponieważ nigdy nie możesz być pewien, że nic nie zostanie dodane do łańcucha prototypów, użyj pętli for, aby wyliczyć tablicę:

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

To napisze:

0 = foo
1 = słupek

16
Tablice obiektami, nie ma „obiektu, który trzyma tablicę”.
RobG

41

W izolacji nie ma nic złego w korzystaniu z for-in na tablicach. I-in iteruje nazwy właściwości obiektu, aw przypadku tablicy „out-of-the-box” właściwości odpowiadają indeksom tablic. (Wbudowany propertes podobnych length, toStringa więc nie są wliczone w iteracji).

Jeśli jednak Twój kod (lub używana struktura) doda niestandardowe właściwości do tablic lub do prototypu tablicy, właściwości te zostaną uwzględnione w iteracji, co prawdopodobnie nie jest tym, czego chcesz.

Niektóre frameworki JS, takie jak Prototype, modyfikują prototyp Array. Inne frameworki, takie jak JQuery, nie mają, więc z JQuery można bezpiecznie używać dla.

Jeśli masz wątpliwości, prawdopodobnie nie powinieneś używać for-in.

Alternatywnym sposobem iteracji po tablicy jest użycie pętli for:

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

Ma to jednak inny problem. Problem polega na tym, że tablica JavaScript może mieć „dziury”. Jeśli zdefiniujesz arrjako:

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

Następnie tablica ma dwa elementy, ale długość 101. Użycie for-in da dwa indeksy, podczas gdy pętla for da 101 indeksów, gdzie 99 ma wartość undefined.


37

Od 2016 roku (ES6) możemy używać for…ofdo iteracji tablic, jak zauważył już John Slegers.

Chciałbym tylko dodać ten prosty kod demonstracyjny, aby wszystko było bardziej zrozumiałe:

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

Konsola pokazuje:

for...of:

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

for...in:

0: 5
1: foo

Innymi słowy:

  • for...ofliczy od 0 do 5, a także ignoruje Array.prototype.foo. Pokazuje wartości tablic .

  • for...inwyświetla tylko 5, ignorując niezdefiniowane indeksy tablic, ale dodaje foo. Pokazuje nazwy właściwości tablicy .


32

Krótka odpowiedź: Po prostu nie warto.


Dłuższa odpowiedź: po prostu nie jest tego warte, nawet jeśli kolejność elementów sekwencyjnych i optymalna wydajność nie są wymagane.


Długa odpowiedź: po prostu nie warto ...

  • Użycie for (var property in array)spowoduje arrayiterację jako obiekt , przemieszczenie łańcucha prototypów obiektu i ostatecznie działanie wolniejsze niż forpętla oparta na indeksie .
  • for (... in ...) nie gwarantuje się zwrócenia właściwości obiektu w kolejności sekwencyjnej, jak można się spodziewać.
  • Używanie hasOwnProperty()i !isNaN()sprawdzanie do filtrowania właściwości obiektu jest dodatkowym narzutem, który powoduje, że działa jeszcze wolniej i neguje kluczowy powód używania go w pierwszej kolejności, tj. Z powodu bardziej zwięzłego formatu.

Z tych powodów akceptowalny kompromis między wydajnością a wygodą nawet nie istnieje. Naprawdę nie ma korzyści, chyba że celem jest obsługa tablicy jako obiektu i wykonywanie operacji na właściwościach obiektu tablicy.


31

Oprócz powodów podanych w innych odpowiedziach, możesz nie chcieć używać struktury „for ... in”, jeśli musisz wykonać matematykę ze zmienną licznika, ponieważ pętla iteruje nazwy właściwości obiektu, a więc zmienna jest ciągiem.

Na przykład,

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

napisze

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

natomiast,

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

napisze

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

Oczywiście można to łatwo obejść przez włączenie

ii = parseInt(ii);

w pętli, ale pierwsza struktura jest bardziej bezpośrednia.


6
Możesz użyć prefiksu +zamiast, parseIntchyba że naprawdę potrzebujesz liczby całkowitej lub zignoruj ​​nieprawidłowe znaki.
Konrad Borowski

Używanie parseInt()nie jest również zalecane. Spróbuj parseInt("025");i to będzie zawieść.
Derek 朕 會 功夫

6
@Derek 朕 會 功夫 - zdecydowanie możesz użyć parseInt. Problem polega na tym, że jeśli nie dodasz podstawki, starsze przeglądarki mogą próbować zinterpretować liczbę (w ten sposób 025 staje się ósemkowa). Zostało to naprawione w ECMAScript 5, ale nadal występuje w przypadku liczb rozpoczynających się od „0x” (interpretuje liczbę jako szesnastkową). Aby być po bezpiecznej stronie, użyj podstawy, aby określić liczbę w ten sposób parseInt("025", 10)- która określa podstawę 10.
IAmTimCorey 16.04.2013

23

Oprócz faktu, że for... inzapętla wszystkie właściwości policzalne (co nie jest tym samym co „wszystkie elementy tablicy”!), Patrz http://www.ecma-international.org/publications/files/ECMA-ST/Ecma -262.pdf , sekcja 12.6.4 (wydanie piąte) lub 13.7.5.15 (wydanie siódme):

Mechanika i kolejność wyliczania właściwości ... nie jest określona ...

(Podkreśl moje.)

Oznacza to, że jeśli przeglądarka tego chce, może przeglądać właściwości w kolejności, w jakiej zostały wstawione. Lub w kolejności numerycznej. Lub w porządku leksykalnym (gdzie „30” występuje przed „4”! Pamiętaj, że wszystkie klucze obiektów - a zatem wszystkie indeksy tablic - są w rzeczywistości łańcuchami, więc to ma sens). Mógłby je przeglądać za pomocą segmentu, jeśli zaimplementował obiekty jako tabele skrótów. Lub weź dowolny z nich i dodaj „wstecz”. Przeglądarka może nawet iterować losowo i być zgodna z ECMA-262, o ile odwiedza każdą właściwość dokładnie raz.

W praktyce większość przeglądarek lubi iterować mniej więcej w tej samej kolejności. Ale nic nie mówi, że muszą. Jest to specyficzne dla implementacji i może ulec zmianie w dowolnym momencie, jeśli okaże się, że inny sposób jest znacznie bardziej wydajny.

Tak czy inaczej for... inniesie ze sobą żadnego związku z porządkiem. Jeśli zależy Ci na porządku, wypowiedz się o nim i użyj zwykłej forpętli z indeksem.


18

Głównie dwa powody:

Jeden

Jak powiedzieli inni, możesz dostać klucze, które nie są w twojej tablicy lub są dziedziczone z prototypu. Więc jeśli, powiedzmy, biblioteka dodaje właściwość do prototypów Array lub Object:

Array.prototype.someProperty = true

Otrzymasz to jako część każdej tablicy:

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

możesz to rozwiązać za pomocą metody hasOwnProperty:

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

ale dotyczy to iteracji po dowolnym obiekcie z pętlą for-in.

Dwa

Zwykle kolejność elementów w tablicy jest ważna, ale pętla for-in niekoniecznie iteruje we właściwej kolejności, ponieważ traktuje tablicę jako obiekt, tak jak jest implementowana w JS, a nie jako tablica. Wydaje się, że to drobiazg, ale naprawdę może zepsuć aplikacje i jest trudny do debugowania.


2
Object.keys(a).forEach( function(item) { console.log(item) } )iteruje tablicę własnych kluczy własności, a nie tych odziedziczonych po prototypie.
Qwerty

2
To prawda, ale podobnie jak w przypadku pętli for-in, niekoniecznie musi być w odpowiedniej kolejności indeksu. Nie działa również w starszych przeglądarkach nieobsługujących ES5.
Lior

Możesz nauczyć tych przeglądarek array.forEach, wstawiając określony kod do swoich skryptów. Zobacz Polyfill developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Qwerty

Oczywiście, ale manipulujesz prototypem, a to nie zawsze jest dobry pomysł ... a jednak masz problem z zamówieniem ...
Lior

I, oczywiście, powód numer trzy: rzadkie tablice.
lepszy oliver

16

Ponieważ wylicza poprzez pola obiektowe, a nie indeksy. Możesz uzyskać wartość z indeksem „długość” i wątpię, czy tego chcesz.


Więc jaki jest najlepszy sposób na zrobienie tego?
lYriCAlsSH

3
for (var i = 0; i <arr.length; i ++) {}
vava

3
W Firefoksie 3 możesz także użyć arr.forEach lub for (var [i, v] w Iterator (arr)) {}, ale żaden z nich nie działa w IE, chociaż możesz samodzielnie napisać metodę forEach.
vava

i praktycznie każda biblioteka ma też własną metodę.
vava

5
Ta odpowiedź jest zła. „długość” nie będzie uwzględniana w iteracji wstępnej. Uwzględnione są tylko właściwości, które sam dodajesz.
JacquesB

16

Nie sądzę, że mam wiele do dodania np. W niektórych przypadkach należy unikać odpowiedzi tryptyku lub odpowiedzi CMS na pytanie, dlaczego for...innależy używać.

Chciałbym jednak dodać, że we współczesnych przeglądarkach istnieje alternatywa, z for...inktórej można korzystać w przypadkach, gdy for...innie można jej użyć. Ta alternatywa to for...of:

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

Uwaga :

Niestety żadna wersja programu Internet Explorer nie obsługuje for...of( Edge 12+ ), więc musisz poczekać trochę dłużej, aż będziesz mógł użyć go w kodzie produkcyjnym po stronie klienta. Jednak użycie kodu JS po stronie serwera powinno być bezpieczne (jeśli używasz Node.js ).


@georgeawg Miałeś na myśli for-ofnie for-in, prawda?
ᆼ ᆺ ᆼ

15

Problem z for ... in ...- a staje się to problemem tylko wtedy, gdy programista tak naprawdę nie rozumie języka; tak naprawdę nie jest to błąd ani nic takiego - polega na tym, że iteruje wszystkie elementy obiektu (cóż, wszystkie elementy policzalne , ale na razie jest to szczegół). Jeśli chcesz iterować tylko indeksowane właściwości tablicy, jedynym gwarantowanym sposobem na zachowanie spójności semantycznej jest użycie indeksu liczb całkowitych (to znaczy for (var i = 0; i < array.length; ++i)pętli stylów).

Każdy obiekt może mieć dowolne właściwości z nim związane. W szczególności nie byłoby nic strasznego w ładowaniu dodatkowych właściwości do instancji tablicy. Kod, który chce widzieć tylko indeksowane właściwości podobne do tablic, musi zatem trzymać się indeksu liczb całkowitych. Kod, który jest w pełni świadomy tego, co for ... inrobi i naprawdę musi zobaczyć wszystkie właściwości, to też w porządku.


Ładne wyjaśnienie Pointy. Po prostu ciekawy. Gdybym miał tablicę, która znajdowała się w obiekcie pod właściwościami mnożenia i czy for inw porównaniu do zwykłej pętli for tablice te zostałyby iterowane? (co w gruncie rzeczy byłoby wolne, prawda?)
NiCk Newman

2
@NiCkNewman dobrze obiekt, do którego się odwołujesz inw for ... inpętli, po prostu
Pointy

Widzę. Ciekawe, ponieważ mam obiekty i tablice wewnątrz mojego głównego obiektu gry i zastanawiałem się, czy dla inningu byłoby to bardziej bolesne niż zwykła pętla for dla indeksów.
NiCk Newman

@NiCkNewman dobrze tematem całego tego pytania jest to, że po prostu nie powinieneś używać for ... inna tablicach; istnieje wiele dobrych powodów, aby tego nie robić. To nie tyle problem z wydajnością, co problem „upewnij się, że się nie złamie”.
Pointy

Cóż, moje obiekty są technicznie przechowywane w tablicy, dlatego się martwiłem, coś w stylu [{a:'hey',b:'hi'},{a:'hey',b:'hi'}]:, ale tak, rozumiem.
NiCk Newman

9

Ponadto ze względu na semantykę sposób, w jaki for, intraktuje tablice (tj. Taki sam jak każdy inny obiekt JavaScript), nie jest dopasowany do innych popularnych języków.

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

9

TL&DR: Używanie for inpętli w tablicach nie jest złe, w rzeczywistości wręcz przeciwnie.

Myślę, że for inpętla jest klejnotem JS, jeśli jest poprawnie używana w tablicach. Oczekuje się, że będziesz mieć pełną kontrolę nad swoim oprogramowaniem i wiesz, co robisz. Zobaczmy wspomniane wady i obalimy je jeden po drugim.

  1. Pętla przechodzi również przez odziedziczone właściwości: Po pierwsze, wszelkie rozszerzenia Array.prototypepowinny zostać wykonane przy użyciu, Object.defineProperty()a ich enumerabledeskryptor powinien być ustawiony na false. Żadna biblioteka, która tego nie robi, nie powinna być w ogóle używana.
  2. Właściwości, które dodasz do łańcucha dziedziczenia, zostaną później policzone: w przypadku podklasowania tablicy Object.setPrototypeOfwedług klasy lub według klasy extend. Należy ponownie użyć Object.defineProperty(), które domyślnie ustawia writable, enumerablei configurabledeskryptorów do własności false. Zobaczmy tutaj podklasę tablic tutaj ...

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

Widzisz więc ... for inpętla jest teraz bezpieczna, ponieważ dbałeś o swój kod.

  1. for inPętla jest powolna: Piekło nie. Jest to zdecydowanie najszybsza metoda iteracji, jeśli zapętlasz rzadkie tablice, które są potrzebne od czasu do czasu. Jest to jedna z najważniejszych sztuczek związanych z wydajnością, którą należy znać. Zobaczmy przykład. Zapętlimy rzadką tablicę.

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


@Ravi Shanker Reddy Dobra konfiguracja testu porównawczego. Jak wspomniałem w mojej odpowiedzi, for inpętla przyćmiewa pozostałe „jeśli” tablica jest rzadka, a tym bardziej, jeśli robi się większa. Więc przestawiłem test laboratoryjny dla rzadkiej tablicy, arro wielkości ~ 10000, z tylko 50 pozycjami losowo wybranymi spośród [42,"test",{t:1},null, void 0]losowych indeksów. Od razu zauważysz różnicę. - >> Sprawdź tutaj << - .
Redu,

8

Oprócz innych problemów składnia „for..in” jest prawdopodobnie wolniejsza, ponieważ indeks jest łańcuchem, a nie liczbą całkowitą.

var a = ["a"]
for (var i in a)
    alert(typeof i)  // 'string'
for (var i = 0; i < a.length; i++)
    alert(typeof i)  // 'number'

Prawdopodobnie nie ma to większego znaczenia. Elementy tablicy są właściwościami obiektu opartego na macierzy lub podobnego do macierzy, a wszystkie właściwości obiektu mają klucze łańcuchowe. O ile silnik JS w jakiś sposób go nie zoptymalizuje, nawet jeśli użyjesz liczby, w wyniku wyszukiwania zostanie przekształcony w ciąg znaków.
cHao

Bez względu na jakiekolwiek problemy z wydajnością, jeśli var i in adopiero zaczynasz korzystać z JavaScript, skorzystaj z indeksu i oczekuj, że indeks będzie liczbą całkowitą, a następnie zrobienie czegoś podobnego a[i+offset] = <value>spowoduje umieszczenie wartości w całkowicie niewłaściwych miejscach. („1” + 1 == „11”).
szmoore

8

Ważnym aspektem jest to, że for...initeruje tylko właściwości zawarte w obiekcie, dla którego ich atrybut właściwości wymiennych jest ustawiony na true. Jeśli więc spróbujemy wykonać iterację nad obiektem, wówczas dowolne właściwości mogą zostać pominięte, jeśli ich atrybut właściwości wyliczalnych ma wartość false. Jest całkiem możliwa zmiana atrybutu właściwości wyliczalnych dla normalnych obiektów Array, aby niektóre elementy nie były wyliczane. Chociaż ogólnie atrybuty właściwości mają zwykle zastosowanie do właściwości funkcji w obiekcie.for...in

Można sprawdzić wartość wyliczalnego atrybutu właściwości przez:

myobject.propertyIsEnumerable('myproperty')

Lub uzyskać wszystkie cztery atrybuty właściwości:

Object.getOwnPropertyDescriptor(myobject,'myproperty')

Jest to funkcja dostępna w ECMAScript 5 - we wcześniejszych wersjach nie można było zmienić wartości atrybutu właściwości wyliczalnych (zawsze była ustawiona na true).


8

for/ inWspółpracuje z dwoma rodzajami zmiennych hashtables (asocjacyjnych) i matrycy (nie asocjacyjną).

JavaScript automatycznie określi sposób, w jaki przechodzi przez elementy. Jeśli więc wiesz, że twoja tablica naprawdę nie jest asocjacyjna, możesz użyć jej for (var i=0; i<=arrayLen; i++)i pominąć iterację automatycznego wykrywania.

Ale moim zdaniem lepiej jest użyć for/ in, proces wymagany do automatycznego wykrywania jest bardzo mały.

Prawdziwa odpowiedź na to zależy od tego, jak przeglądarka analizuje / interpretuje kod JavaScript. Może zmieniać się między przeglądarkami.

Nie mogę wymyślić innych celów, aby nie używać 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]);

prawda, chyba że używasz prototypowanych obiektów. ;) poniżej
Ricardo

To dlatego, że Arrayjest Objectteż
bezpłatne konsultacje

2
for ... inwspółpracuje z obiektami. Nie ma czegoś takiego jak automatyczne wykrywanie.
lepszy oliver

7

Ponieważ będzie to powtarzać właściwości należące do obiektów w łańcuchu prototypów, jeśli nie będziesz ostrożny.

Możesz użyć for.. in, po prostu sprawdź każdą właściwość hasOwnProperty .


2
Niewystarczająco - dodawanie dowolnych nazwanych właściwości do instancji tablic jest w porządku, a te będą testować na truepodstawie hasOwnProperty()kontroli.
Pointy

Dobra uwaga, dzięki. Nigdy sam nie byłem na tyle głupi, aby zrobić to z tablicą, więc nie zastanawiałem się nad tym!
JAL,

1
@Pointy Nie testowałem tego, ale być może można to obejść, isNaNsprawdzając nazwę każdej właściwości.
WynandB

1
@Wynand ciekawy pomysł; jednak tak naprawdę nie rozumiem, dlaczego warto kłopotać się, gdy iteracja z prostym indeksem numerycznym jest tak łatwa.
Pointy

@WynandB przepraszam za guz, ale czuję, że poprawka jest w porządku: isNaNsłuży do sprawdzania, czy zmienna jest wartością specjalną NaN, czy nie, nie można jej używać do sprawdzania „rzeczy innych niż liczby” (możesz użyć zwykłego typeof do tego).
doldt

6

To nie jest koniecznie złe (w oparciu o to, co robisz), ale w przypadku tablic, jeśli coś zostało dodane do Array.prototype, następnie masz zamiar dostać dziwne wyniki. Gdzie można oczekiwać, że ta pętla uruchomi się trzy razy:

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

Jeśli funkcja o nazwie helpfulUtilityMethodzostał dodany do Array„s prototype, wówczas pętla zakończy się działa cztery razy: keybyłoby 0, 1, 2, i helpfulUtilityMethod. Jeśli spodziewałeś się tylko liczb całkowitych, ups.


6

Należy używać for(var x in y)tylko na listach właściwości, a nie na obiektach (jak wyjaśniono powyżej).


13
Tylko uwaga na temat SO - nie ma „powyżej”, ponieważ komentarze cały czas zmieniają kolejność na stronie. Tak naprawdę nie wiemy, jaki masz na myśli komentarz. Z tego powodu dobrze jest powiedzieć „w komentarzu x osoby”.
JAL

@JAL ... lub dodaj bezpośredni link do odpowiedzi.
WynandB

5

Używanie for...inpętli do tablicy nie jest złe, chociaż mogę zgadnąć, dlaczego ktoś ci to powiedział:

1.) Istnieje już funkcja wyższego rzędu lub metoda, która ma ten cel dla tablicy, ale ma większą funkcjonalność i krótszą składnię, zwaną „forEach”: Array.prototype.forEach(function(element, index, array) {} );

2.) Tablice zawsze mieć długość, ale for...ini forEachnie realizują funkcję dla dowolnej wartości, która jest 'undefined'jedynie dla indeksów, które mają określoną wartość. Jeśli więc przypiszesz tylko jedną wartość, te pętle wykonają funkcję tylko raz, ale ponieważ tablica jest wyliczona, zawsze będzie miała długość do najwyższego indeksu, który ma zdefiniowaną wartość, ale ta długość może pozostać niezauważona podczas korzystania z nich pętle

3.) Standard dla pętli wykona funkcję tyle razy, ile zdefiniujesz w parametrach, a ponieważ tablica jest ponumerowana, bardziej sensowne jest określenie, ile razy chcesz wykonać funkcję. W przeciwieństwie do innych pętli, pętla for może następnie wykonać funkcję dla każdego indeksu w tablicy, niezależnie od tego, czy wartość jest zdefiniowana, czy nie.

Zasadniczo możesz użyć dowolnej pętli, ale pamiętaj dokładnie, jak działają. Zrozum warunki, w których powtarzają się różne pętle, ich oddzielne funkcje i zdaj sobie sprawę, że będą one mniej lub bardziej odpowiednie dla różnych scenariuszy.

Można również uznać za lepszą praktykę stosowanie tej forEachmetody niż for...inogólnie pętli, ponieważ łatwiej jest pisać i ma więcej funkcji, więc możesz przyzwyczaić się do używania tej metody i standardu, ale połączenie.

Zobacz poniżej, że dwie pierwsze pętle wykonują instrukcje console.log tylko raz, podczas gdy standardowa pętla wykonuje tę funkcję tyle razy, ile określono, w tym przypadku 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]

4

Oto powody, dla których jest to (zwykle) zła praktyka:

  1. for...inpętle iterują wszystkie swoje własne wyliczalne właściwości i wyliczalne właściwości ich prototypu (ów). Zwykle w iteracji tablicy chcemy iterować tylko nad samą tablicą. I nawet jeśli sam nie możesz niczego dodać do tablicy, twoje biblioteki lub framework mogą coś dodać.

Przykład :

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...inPętle nie gwarantują określonej kolejności iteracji . Chociaż w dzisiejszych czasach większość przeglądarek jest zwykle widoczna, wciąż nie ma 100% gwarancji.
  2. for...inPętle ignorują undefinedelementy tablicy, tj. elementy tablicy, które nie zostały jeszcze przypisane.

Przykład :

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 jest przydatny podczas pracy nad obiektem w JavaScript, ale nie dla tablicy, ale nadal nie możemy powiedzieć, że jest to zły sposób, ale nie jest to zalecane, spójrz na poniższy przykład za pomocą 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 

OK, zróbmy to teraz z Array :

let txt = "";
const person = ["Alireza", "Dezfoolian", 35]; 
for (const x in person) {
   txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 

Jak widzisz wynik ten sam ...

Ale spróbujmy czegoś, prototypujmy coś do Array ...

Array.prototype.someoneelse = "someoneelse";

Teraz tworzymy nową 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

Widzisz someoneelse !!! ... W tym przypadku przeglądamy nowy obiekt Array!

Więc to jest jeden z powodów, dlaczego musimy użyć for..in starannie, ale nie zawsze jest to przypadek ...


2

Pętla for ... in zawsze wylicza klucze. Klucze właściwości obiektów są zawsze ciągiem, nawet indeksowane właściwości tablicy:

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


0

chociaż nie zostało to szczegółowo omówione w tym pytaniu, dodam, że istnieje bardzo dobry powód, aby nigdy nie używać ... w połączeniu z NodeList(jak można uzyskać z querySelectorAllwywołania, ponieważ w ogóle nie widzi zwróconych elementów) iteracja tylko nad właściwościami NodeList.

w przypadku pojedynczego wyniku otrzymałem:

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

co wyjaśniało, dlaczego mój for (node in nodes) node.href = newLink;zawodził.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.