Cicli JavaScript: per … in vs per

Ho affrontato uno strano comportamento in Javascript. ottengo

“L’object non supporta questa proprietà o metodo”

eccezione per la funzione removeAttribute nel seguente codice:

 var buttons = controlDiv.getElementsByTagName("button"); for ( var button in buttons ) button.removeAttribute('disabled'); 

Quando cambio il codice con il seguente, il problema scompare:

 var buttons = controlDiv.getElementsByTagName("button"); for ( var i = 0; i < buttons.length; i++ ) buttons[i].removeAttribute('disabled'); 

Qual è il valore del button all’interno di for...in ?

Non usare for..in per l’iterazione di matrice.

È importante capire che la syntax della parentesi quadra quadrata ( [] ) di Javascript Array per l’accesso agli indici è in realtà ereditata dall’object …

 obj.prop === obj['prop'] // true 

La struttura for..in non funziona come un tradizionale for..each/in che potrebbe essere trovato in altre lingue (php, python, ecc …).

Javascript for..in è progettato per iterare sulle proprietà di un object . Produrre la chiave di ogni proprietà. Usando questo tasto combinato con la syntax della parentesi Object , puoi facilmente accedere ai valori che stai cercando.

 var obj = { foo: "bar", fizz: "buzz", moo: "muck" }; for ( var prop in obj ) { console.log(prop); // foo / fizz / moo console.log(obj[prop]); // bar / buzz / muck } 

E poiché l’array è semplicemente un object con nomi di proprietà numeriche sequenziali (indici), for..in funziona in modo simile, producendo gli indici numerici proprio come produce i nomi di proprietà sopra.

Una caratteristica importante della struttura for..in è che continua a cercare le proprietà enumerabili sulla catena del prototipo. Inoltre, itererà le proprietà enumerabili ereditate . Spetta a te verificare che la proprietà corrente esista direttamente sull’object locale e non sul prototipo a cui è collegato con hasOwnProperty()

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

( Altro su Prototypal Inheritance )

Il problema con l’uso della struttura for..in sul tipo Array è che non esiste alcuna garaunte su quale ordine vengono prodotte le proprietà … e in generale si tratta di una caratteristica molto importante nell’elaborazione di un array.

Un altro problema è che di solito è più lento di uno standard for implementazione.

Linea di fondo

Usare un for...in per iterare gli array è come usare il calcio di un cacciavite per guidare un chiodo … perché non dovresti usare solo un martello ( for )?

for...in deve essere utilizzato quando si desidera eseguire il looping sulle proprietà di un object. Funziona allo stesso modo di un ciclo normale: la variabile loop contiene l’attuale “indice”, che indica la proprietà dell’object e non il valore.

Per eseguire iterazioni su array, è necessario utilizzare un ciclo for normale. buttons non sono un array ma un NodeList (una matrice come una struttura).

Se itera su buttons con for...in con:

 for(var i in a) { console.log(i) } 

Vedrai che emette qualcosa come:

 1 2 ... length item 

perché la length e l’ item sono due proprietà di un object di tipo NodeList . Quindi, se dovessi usare ingenuamente for..in , dovresti provare ad accedere ai buttons['length'].removeAttribute() che genererà un errore in quanto i buttons['length'] sono una funzione e non un elemento DOM.

Quindi il modo corretto è usare un ciclo normale for . Ma c’è un altro problema:

NodeList sono live, ovvero ogni volta che si accede ad es. NodeList length , la lista viene aggiornata (gli elementi vengono ricercati di nuovo). Pertanto è necessario evitare chiamate inutili alla length .

Esempio:

 for(var i = 0, l = buttons.length; i < l, i++) 

for(var key in obj) { } itera su tutti gli elementi nell’object, compresi quelli dei suoi prototipi. Quindi se lo stai usando e non puoi sapere nulla di Object.prototype esteso dovresti sempre testare obj.hasOwnProperty(key) e saltare la chiave se questo controllo restituisce false.

for(start; continuation; loop) è un ciclo in stile C: l’ start viene eseguito prima del ciclo, la continuation viene verificata e il ciclo continua finché è vero, il loop viene eseguito dopo ogni ciclo.

Mentre for..in non dovrebbe essere generalmente utilizzato per gli array, tuttavia prima di ES5 c’era un caso per utilizzarlo con array sparsi.

Come notato in altre risposte, i problemi principali con for..in e Arrays sono:

  1. Le proprietà non sono necessariamente restituite in ordine (ovvero non 0, 1, 2 ecc.)
  2. Vengono restituite tutte le proprietà enumerabili, incluse le proprietà non indice e quelle sulla catena [[Prototype]] . Questo porta a prestazioni inferiori poiché un test hasOwnProperty è probabilmente necessario per evitare proprietà ereditate.

Una delle ragioni per cui utilizzare prima di ES5 era migliorare le prestazioni con array sparsi, a condizione che l’ordine non contenga. Ad esempio, nel seguito:

 var a = [0]; a[1000] = 1; 

L’iterazione su un uso di for..in sarà molto più veloce dell’utilizzo di un ciclo for, poiché visiterà solo due proprietà mentre un ciclo for proverà 1001.

Tuttavia, questo caso è reso ridondante da forEach di ES5, che visita solo i membri esistenti, quindi:

 a.forEach(); 

anche solo iterare su due proprietà, in ordine.