Enumerare le proprietà di un oggetto in Javascript

Nella lezione precedente abbiamo visto come verificare l'esistenza di una proprietà in un oggetto Javascript. In questa lezione vediamo, invece, come iterare attraverso tutte le proprietà di un oggetto, in gergo tecnico come enumerarle.

Ciclo for/in

Il modo più semplice per iterare attraverso le proprietà di un oggetto è quello di usare un ciclo for/in. Vediamo l'esempio:

1
2
3
4
5
6
7
8
9
let A = {
    x: 1,
    y: 2,
    z: 3
};

for (let prop in A) {
    console.log(`A[${prop}] = ${A[prop]}`);
}

L'output di questo esempio sarà:

A[x] = 1
A[y] = 2
A[z] = 3

La riga che ci interessa è la 7. In essa usiamo la variabile prop per iterare attraverso le proprietà di A. Nel corpo del ciclo, la variabile prop conterrà, sotto forma di stringa, gli identificatori delle proprietà di A. Nella riga 8 usiamo prop per accedere alle singole proprietà.

Usando il ciclo for/in è anche possibile iterare attraverso le proprietà ereditate dal prototipo. Guardiamo l'esempio seguente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let A = {
    x: 1,
    y: 2,
    z: 3
};

let B = Object.create(A);
B.w = 4;

for (let prop in B) {
    console.log(`B[${prop}] = ${B[prop]}`);
}

L'output di questo esempio sarà:

B[w] = 4
B[x] = 1
B[y] = 2
B[z] = 3

Come si può osservare, oltre alla own property di B, troviamo anche le proprietà x, y e z di A. Il ciclo for/in, tuttavia, non itererà attraverso le proprietà non enumerabili, ossia quelle proprietà per cui l'attributo enumerable è a false. Un esempio è la funzione hasOwnProperty ereditata da Object che, infatti, non appare nell'output degli esempi di sopra.

L'esempio precedente può essere anche esteso per evitare di iterare attraverso proprietà ereditate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let A = {
    x: 1,
    y: 2,
    z: 3
};

let B = Object.create(A);
B.w = 4;

for (let prop in B) {
    if (!B.hasOwnProperty(prop)) continue;
    console.log(`B[${prop}] = ${B[prop]}`);
}

L'output sarà:

B[w] = 4

Infatti, verrà mostrato a schermo soltanto la proprietà w che è una own property di B.

Javascript mette, inoltre, a disposizione alcune funzioni di utilità per enumerare le proprietà:

  • Object.keys(): restituisce in un array tutte le own property di un oggetto. Non restituisce le proprietà non enumerabili e quelle ereditate. Ad esempio:
    let props = Object.keys(B);
    for (let prop_index in props) {
        console.log(`B[${props[prop_index]}] = ${B[props[prop_index]]}`);
    }
    
  • Object.getOwnPropertyNames(): Simile a keys ma restituisce anche le own properties non enumerabili.

Ordine di enumerazione

Abbiamo visto che gli oggetti sono assimilabili a dei dizionari dove le proprietà sono coppie nomi/valore. Tuttavia, Javascript mette a disposizione anche l'oggetto Map che funge da dizionario vero e proprio, per cui utilizzare un oggetto come dizionario puro è uno sbaglio in quanto non così performante come una Map.

Analogamente è un errore affidarsi all'ordine di iterazione delle proprietà. Se scriviamo un programma che si basa sull'ordine esatto con cui scorriamo le proprietà in un ciclo non è detto che tra diversi interpreti Javascript quest'ordine sia rispettato.

Nella revisione 6 del linguaggio, ES6, si è provato a standardizzare l'ordine di enumerazione per le funzioni keys e getOwnPropertyNames. L'ordine è il seguente:

  1. Prima vengono restituite tutte le proprietà i cui nomi sono interi non negativi. Queste proprietà verranno restituite in ordine numerico proprio come in un array.
  2. Successivamente verranno restituite tutte altre proprietà nell'ordine in cui sono state aggiunte all'oggetto.

Vediamo un esempio:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let A = {
    z: 3,
    y: 2,
    x: 1
};

A[0] = 4;
A[1] = 5;
A[-1] = 6;
A[3.1415] = 7;

let props = Object.keys(A);
for (let prop_index in props) {
    console.log(`A[${props[prop_index]}] = ${A[props[prop_index]]}`);
}

L'output di questo esempio sarà:

A[0] = 4
A[1] = 5
A[z] = 3
A[y] = 2
A[x] = 1
A[-1] = 6
A[3.1415] = 7

Come si può vedere, prima verranno restituite le proprietà con indice assimilabile a interi non negativi: 0 e 1. Poi verranno restituite tutte le proprietà nell'ordine in cui sono state aggiunte ad A.

Per quanto riguarda invece l'uso di for/in, purtroppo, non è specificato nello standard e le diverse implementazioni potrebbero applicare un ordine diverso. Tipicamente, però, le implementazioni cercano di rispettare lo stesso ordine di sopra per le own property per poi passare alle proprietà del prototipo e così via. Per meglio comprendere, guardiamo l'esempio:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let A = {
    z: 3,
    y: 2,
    x: 1
};

A[0] = 4;
A[1] = 5;
A[-1] = 6;
A[3.1415] = 7;

let B = Object.create(A);
B.w = 8;
B[2] = 10;
B[1] = 9;

for (let prop in B) {
    console.log(`B[${prop}] = ${B[prop]}`);
}

L'output, utilizzando Node.js, sarà:

B[1] = 9
B[2] = 10
B[w] = 8
B[0] = 4
B[z] = 3
B[y] = 2
B[x] = 1
B[-1] = 6
B[3.1415] = 7

Due cose bisogna notare nell'output:

  1. Vengono dapprima enumerate le own property di B poi le proprietà del prototipo A.
  2. Dato che la proprietà 1 è presente in B e sovrascrive la stessa proprietà di A, essa verrà enumerata solo una volta. Per cui le proprietà sovrascritte non vengono enumerate ma soltanto le sovrascriventi.

Riassumendo

In questa lezione abbiamo visto come enumerare le proprietà di un oggetto in Javascript, ossia come iterare attraverso di esse con l'ausilio del ciclo for/in e come utilizzare le funzioni Object.keys e Object.getOwnPropertyNames.

Inoltre abbiamo visto quale sia l'ordine di enumerazione e come, in alcuni, casi esso dipenda dall'implementazione dell'interprete Javascript, per cui affidarsi ad esso è un errore di programmazione.

Nella prossima lezione vedremo come estendere gli oggetti, ossia copiare le proprietà di un oggetto in un altro.