follow me on Twitter

    Javascript Array Iteration... la tentazione del for... in...

    la tentazione è forte...
    
    my_array = [2,3,4];
    
    for (var i in my_array) {
    print(i);
    }
    
    purtroppo... non avevate considerato questo... ;-)
    
    >>> Array.prototype.remove = function (B) {
     var A = this.indexOf(B);
     if (A != -1) {
         this.splice(A, 1);
     }
     return this;
    }
    >>> my_array.remove
    function()
    >>> for (var i in my_array) { console.log(i) }
    0
    1
    2
    remove
    
    dopo aver aggiunto un metodo al prototype di Array ce lo ritroveremo in tutte le istanze di Array:
    DOH!!!!
    ehhhh! non vi lamentate troppo pero'... il manuale parlava chiaro ;-)
    
    (From "Core JavaScript 1.5 Reference:Statements:for...in")
    Although it may be tempting to use this as a way to iterate over an Array,
    this is a bad idea.
    The for...in statement iterates over user-defined properties in addition
    to the array elements, so if you modify the array's non-integer or non-positive
    properties (e.g. by adding a "foo" property to it or even by adding a method or
    property to Array.prototype), the for...in statement will return the name of
    your user-defined properties in addition to the numeric indexes.
    Also, because order of iteration is arbitrary, iterating over an array may not
    visit elements in numeric order.
    Thus it is better to use a traditional for loop with a numeric index when
    iterating over arrays.
    
    Quindi scartate for e for each come metodi di iterazioni sugli Array Javascript... toglietevelo dalla testa ;-) (sono più utili per fare reflection sugli oggetti... Array compresi).
    Il modo corretto di iterare un array sino a Javascript 1.6 era il ciclo for con indice numerico (alla plain C maniera) mentre ora è possibile usare il metodo forEach:
    
    >>> my_array.forEach(function(e) { console.log(e); });
    2
    3
    4
    >>> Array.forEach(my_array,function(e) { console.log(e); });
    2
    3
    4
    
    Ma attenzione a quello che scrivete nella closure che passate a forEach:
    
    >>> obj = { itera: function(a) { console.log(this); a.forEach(function(e) { console.log(this); }) } }
    Object
    >>> obj.itera([1,2,3]);
    Object
    Window
    Window
    Window
    
    come è possibile verificare nel proprio Firebug this fuori dalla closure punta all'Object obj, mentre il this nella closure punta a Window (il contesto globale). Un'ulteriore conferma di questo comportamento possiamo ottenerla con uno spidermonkey in linea di comando:
    
    rpl@ubik:~$ js
    js> obj = { itera: function(a) { print(this); a.forEach(function(e) { print(this); }) } }
    [object Object]
    js> obj.itera([1,2,3])
    [object Object]
    [object global]
    [object global]
    [object global]
    
    Questo comportamento è un po' spiazzante (con un for o un for each non avremmo avuto un cambio di contesto del genere). Personalmente risolvevo definendo come workaround una variabile self raggiungibile dalla closure per ricordare il contesto da cui ho avviato il forEach:
    
    >>> obj = {
     prova: function(a) {
       var self = this;
       console.log(this);
       a.forEach(function(e) { console.log(self); })
     }
    }
    Object
    >>> obj.prova([1,2,3])
    Object
    Object
    Object
    Object
    
    Ma ancora una volta facendo riferimento al manuale (RTFM RTFM RTFM RTFM!!!): si può verificare che forEach accetta un secondo parametro allo scopo di selezionare il contesto (this) nel quale la closure sarà eseguita, che se non definito o nullo sarà associato al contesto globale (come verificato con gli snippet precedenti). Detto in codice diventa:
    
    >>> obj = {
      itera: function(a) {
          console.log(this);
          a.forEach(function(e) { console.log(this); },this)
      }
    }
    Object
    >>> obj.itera([1,2,3])
    Object
    Object
    Object
    Object
    
    Quindi la Soluzione (quella con la S maiuscola) è quella di passare this come secondo parametro della forEach.
    Quante cose si imparano leggendo un manuale, no? ;-)

    0 commenti:

    View Luca Greco"s profile on LinkedIn

    Rpl

    La mia foto
    Lecce, Italy
    Fulltime Coder and *nix BOFH