Modifica degli oggetti ECMAScript

Utilizzando ECMAScript, è possibile non solo creare oggetti, ma anche modificare il comportamento degli oggetti esistenti.

L'attributo 'prototype' può non solo definire le proprietà e i metodi del costruttore, ma anche aggiungere proprietà e metodi agli oggetti locali.

Creare nuovi metodi

Creare nuovi metodi attraverso i metodi esistenti

È possibile definire nuovi metodi per qualsiasi classe esistente utilizzando l'attributo prototype, come se si trattasse della propria classe. Ad esempio, ricordi il metodo toString() della classe Number? Se gli si passa un parametro 16, restituisce una stringa esadecimale. Se il parametro è 2, restituisce una stringa binaria. Possiamo creare un metodo che converte direttamente un oggetto numero in una stringa esadecimale. Creare questo metodo è molto semplice:

Number.prototype.toHexString = function() {
  return this.toString(16);
};

In questo ambiente, la parola chiave this si riferisce all'istanza di Number, quindi possiamo accedere a tutti i metodi di Number. Con questo codice, possiamo eseguire l'operazione seguente:

var iNum = 15;
alert(iNum.toHexString()); // Output "F"

TIY

Poiché il numero 15 è uguale a F in esadecimale, il messaggio di avviso sarà "F".

Rinominare i metodi esistenti

Possiamo anche rinominare i metodi esistenti con nomi più comprensibili. Ad esempio, possiamo aggiungere due metodi enqueue() e dequeue() alla classe Array, che chiamano solo i metodi esistenti push() e shift():

Array.prototype.enqueue = function(vItem) {
  this.push(vItem);
};
Array.prototype.dequeue = function() {
  return this.shift();
};

TIY

Aggiungere metodi non correlati ai metodi esistenti

Certo, è possibile aggiungere metodi che non hanno nulla a che fare con i metodi esistenti. Ad esempio, supponiamo di voler determinare la posizione di un elemento nell'array senza metodo locale per farlo. Possiamo facilmente creare il seguente metodo:

Array.prototype.indexOf = function (vItem) {
  for (var i=0; i<this.length; i++) {
    if (vItem == this[i]) {
	  return i;
	}
  }
  return -1;
}

Questo metodo indexOf() è coerente con il metodo omonimo della classe String, ricerca ogni elemento dell'array fino a trovare l'elemento fornito. Se trova l'elemento, restituisce la posizione dell'elemento; altrimenti, restituisce -1. Con questa definizione, possiamo scrivere il seguente codice:

var aColors = new Array("red","green","blue");
alert(aColors.indexOf("green"));	// Output: "1"

TIY

Aggiungere nuovi metodi agli oggetti locali

Infine, per aggiungere un nuovo metodo a ogni oggetto locale in ECMAScript, è necessario definirlo sulla proprietà prototype dell'oggetto Object. Come abbiamo spiegato nei capitoli precedenti, tutti gli oggetti locali ereditano l'oggetto Object, quindi qualsiasi modifica all'oggetto Object si rifletterà su tutti gli oggetti locali. Ad esempio, se si desidera aggiungere un metodo che avvisa della valore corrente dell'oggetto, si può utilizzare il seguente codice:

Object.prototype.showValue = function () {
  alert(this.valueOf());
};
var str = "hello";
var iNum = 25;
str.showValue();		// Output: "hello"
iNum.showValue();		// Output: "25"

TIY

In questo caso, gli oggetti String e Number ereditano il metodo showValue() dall'oggetto Object, chiamando questo metodo sugli oggetti dei loro rispettivi oggetti, mostreranno "hello" e "25".

Ridefinire i metodi esistenti

Come possiamo definire nuovi metodi per le classi esistenti, possiamo anche ridefinire i metodi esistenti. Come descritto nei capitoli precedenti, il nome della funzione è solo un puntatore alla funzione, quindi può essere facilmente puntato a un altro funzione. Se si modifica un metodo locale, come toString(), cosa potrebbe accadere?

Function.prototype.toString = function() {
  return "Function code hidden";
}

Il codice precedente è completamente legale e il risultato è completamente conforme alle aspettative:

function sayHi() {
  alert("hi");
}
alert(sayHi.toString());	// Output: "Function code hidden"

TIY

Forse ti ricordi, nel capitolo sull'oggetto Function è stato introdotto il metodo toString() che di solito restituisce il codice sorgente della funzione. Sostituire questo metodo può restituire un altro stringa (in questo esempio, può restituire "Function code hidden"). Tuttavia, cosa è successo alla funzione originale a cui punta toString()? Sarà recuperata dal programma di recupero dello spazio inutilizzato perché è stata completamente abbandonata. Non esiste un metodo per ripristinare la funzione originale, quindi è più sicuro salvare il suo puntatore prima di sostituire il metodo originale, in modo da poterlo utilizzare in futuro. A volte potresti persino chiamare il metodo originale nel nuovo metodo:

Function.prototype.originalToString = Function.prototype.toString;
Function.prototype.toString = function() {
  if (this.originalToString().length > 100) {
    return "La funzione è troppo lunga per essere visualizzata.";
  } else {
    return this.originalToString();
  }
};

TIY

In questo pezzo di codice, la prima riga salva il riferimento al metodo toString() corrente nell'attributo originalToString. Poi sovrascrive il metodo toString() con un metodo personalizzato. Il nuovo metodo verifica se la lunghezza del codice della funzione è maggiore di 100. Se lo è, restituisce un messaggio di errore che dice che il codice della funzione è troppo lungo, altrimenti chiama il metodo originalToString() e restituisce il codice sorgente della funzione.

Binding molto tardi (Very Late Binding)

Technicamente, non esiste il binding molto tardi. Questo libro utilizza il termine per descrivere un fenomeno in ECMAScript, ovvero la possibilità di definire i metodi dell'oggetto dopo la sua istanziazione. Ad esempio:

var o = new Object();
Object.prototype.sayHi = function () {
  alert("hi");
};
o.sayHi();

TIY

In molti linguaggi di programmazione, è necessario definire i metodi dell'oggetto prima di istanziare l'oggetto. Qui, il metodo sayHi() è stato aggiunto dopo aver creato un'istanza della classe Object. In linguaggi tradizionali, non si è mai sentiti parlare di questa operazione né di come il metodo possa essere automaticamente assegnato all'istanza dell'oggetto Object e utilizzato immediatamente (riguardo alla riga successiva).

Attenzione:Non si consiglia di utilizzare il metodo di binding molto tardi, poiché è difficile tracciarlo e registrarlo. Tuttavia, è ancora necessario comprendere questa possibilità.