Array di oggetti: copiare con cura

Ieri giocavo con il mio ultimo passatempo: D3, una fantastica libreria javascript per visualizzare dati, il cui concetto centrale è la capacità di mappare dati nel DOM in modo che elementi di quest’ultimo assumano proprietà grafiche (dimensione, posizione, colore, comportamento, etc) in funzione dei valori abbinati.

Tra le altre cose, le API comprendono funzioni di utilità, alcune delle quali modificano i dataset (cioè l’insieme dei valori da rappresentare) rendendoli adatti alla successiva fase di binding con il DOM.

Una di queste mi ha colpevolmente tenuto impegnato diverse ore mostrando un comportamento incomprensibile. Colpevolmente nel senso che era solo colpa mia e della mia leggerezza nel maneggiare una struttura un po’ più complicata.

Il dataset di partenza è un array multidimensionale i cui valori più interni sono oggetti

var dataset = [
   [
      { x: 0, y: 5 },
      { x: 1, y: 4 },
      { x: 2, y: 2 },
      { x: 3, y: 7 },
      { x: 4, y: 23 }
   ],
   [
      { x: 0, y: 10 },
      { x: 1, y: 12 },
      { x: 2, y: 19 },
      { x: 3, y: 23 },
      { x: 4, y: 17 }
  ],
  [
      { x: 0, y: 22 },
      { x: 1, y: 28 },
      { x: 2, y: 32 },
      { x: 3, y: 35 },
      { x: 4, y: 43 }
  ]
];

Come si nota si tratta di un array di tre elementi, ciascuno dei quali è a sua volta un array contenente 5 oggetti. Ciascuno degli oggetti annidati ha a sua volta due proprietà (x, y).

Per esigenze di debug volevo verificare cosa accadesse all’array una volta dato in pasto a una funzione il cui result era lo stesso array modificato con l’aggiunta di una proprietà y0 in ciascun oggetto.

Per farlo avevo provveduto a creare una copia dell’array a monte del passaggio alla funzione, tramite il metodo .slice():

var datasetCopy = dataset.slice();

Non vi dico la sorpresa quando scopro che secondo la console di Chrome la copia dell’array risulta modificata dalla funzione di cui sopra: inizio ad arrovellarmi sulle possibili cause, non escluse bug della console, bug della libreria, spostamento dell’asse terrestre. Ovviamente il problema ero io.

E in effetti la questione è piuttosto banale, come a un certo punto intuisce il mio amico Max: il metodo .slice() crea davvero una copia di un array, nel senso che crea un altro “oggetto” array in memoria, diverso dall’originale, e non il puntatore a quel riferimento. Il problema nasce sul contenuto dell’array: se si tratta di oggetti, viene copiato il riferimento e non vengono invece creati nuovi oggetti. Per cui, pure avendo due array formalmente distinti, il relativo contenuto risulta in comune.

Di conseguenza, in un simile caso per avere una copia effettiva, completamente distinta dall’originale, conviene ciclare il contenuto di partenza e inserirlo in un nuovo array vuoto. Sotto, la soluzione applicata nello specifico, consistente in due for loop annidati:

var datasetCopy = new Array();

for (var i=0; i < dataset.length; i++ ){
   datasetCopy[i] = new Array();
   for (var p in dataset[i]){
      datasetCopy[i].push({x: dataset[i][p].x , y: dataset[i][p].y});
   }
}

In conclusione, attenti agli array con gli oggetti. Alle volte non è sufficiente copiare con metodo.