Copie d'objet JavaScript: référence, avatar et double

Selon la façon dont on assigne un objet à une variable, on obtient une référence au même objet, ou un avatar, ou une copie distincte.

Ne pas savoir à quel type d'alter ego on a affaire peut occasionner des maux de tête à tenter de comprendre le fonctionnement du programme...

Nous allons envisager chaque cas et vérifier par un exemple son comportement.

L'objet initial est une simple paire de coordonnées que l'appelle "point".

var Point = {
"x" : 10,
"y" : 10
}

Référence et assignement

Quand on assigne un objet à une variable on obtient une nouvelle référence à l'objet. Autrement dit la variable est juste un nouveaux nom pour le même objet. Si on modifie le contenu de la variable, l'objet initial est modifié, et vice-verse. Vérification.

var p = Point
p.x = 20
document.write(Point.x)

Point.y = 20
document.write(p.y)

Si l'on change x dans la copie, cela change x aussi dans l'original, et quand on change y dans l'original, cela change y dans le double: p et Point sont deux références au même objet.

Avatar avec Object.create

Cette méthode ajoutée dans ECMAScript 6 permet de produire une copie d'un objet. Mais comme on va le vérifier, il s'agit en fait d'un avatar: quand on modifie l'objet d'origine, cela modifie aussi la copie!

var avatar = Object.create(Point)
Point.y = 63
document.write(avatar.y)

La valeur 63 assigné à y dans l'objet originel est aussi assignée à y dans le double.

On modifie maintenant l'avatar pour voir si cela modifie aussi l'origine.

avatar.x = 250
document.write(Point.x)

Ce n'est pas le cas, la propriété x de Point valait 20 avant l'opération et conserve cette valeur.

Et si on change x dans avatar puis qu'on modifie la même propriété dans l'original?

avatar.x = 250
Point.x = 400
document.write(avatar.x)

L'objet avatar cette fois n'est pas affecté par la modification de l'original.

On peut donc créer des avatars authentiques en JavaScript, mais ces avatars gagnent leur indépendance quand ils sont modifiés!

Instance indépendante avec new

On peut vouloir aussi utiliser des objets comme modèles, qui sans que les copies soient modifiées avec l'original. On utilise alors plutôt une fonction qui est aussi un objet en JavaScript.

function FPoint() {
  this.x = 5
  this.y = 5
}
var inst = new FPoint()
FPoint.x = 500
document.write("inst.x = " + inst.x)
document.write("FPoint.x = " + FPoint.x)

On voit que les attributs de l'instance ne sont pas modifiés.

Essayons aussi d'afficher la valeur de l'attribut y:

document.write("FPoint.y = " + FPoint.y)

Le résultat est indéfini. La valeur de l'attribut ne peut être affichée. La valeur de x n'est donc pas celle de l'attribut de la fonction, mais plutôt celle d'un attribut x qui est ajouté à la variable FPoint à laquelle on avait assigné le corps de la fonction!

Pour le vérifier, créons une nouvelle instance après avoir assigné x.

var inst2 = new FPoint()
document.write("inst2.x = " + inst2.x)

La valeur de x est toujours celle de la définition originelle.

En conclusion, le corps d'une fonction peut servir de modèle pour créer des objets mais ne se comporte pas comme un objet.

On peut cependant ajouter dynamiquement des attributs à l'original comme prototypes, ils feront alors aussi partie des instances, même si les instances sont déclarées avant que ces attributs ne soient définis.

FPoint.prototype.z = 333
document.write(inst.z)

Dans ce cas, l'instance se comporte de nouveau comme un avatar de l'objet original, alors que si on ajoute des attributs sans en faire des prototypes, cela n'affecte pas les instances, les copies sont indépendantes.

Utiliser un constructeur

Si l'on définit un objet sous forme de fonction, il est aussi possible de passer des paramètres. Dans ce cas, la copie se déclare comme une instance de classe et fonctionne comme telle.

var FPoint2 = function(nx) {
  this.x = nx
  this.y = 10
}

La référence à x dans l'instance doit retourner 50, tandis que la référence à x dans FPoint2 serait indéfinie car il se comporte comme une fonction.

var FPoint2 = function(nx) {
  this.x = nx
  this.y = 10
}
var fp2 = new FPoint2(50)
document.write("fp2.x = " + fp2.x)

Les attributs ne FPoint2 ne peuvent dans ce cas être obtenus qu'en créant une instance.

Copie d'un objet simple

Pour dédoubler un objet simple sans constructeur et obtenir une copie distincte et indépendante, une astuce est d'utiliser JSON.

var Point2 = {
  x: 10,
  y: 10
}

var p2 = JSON.parse(JSON.stringify(Point2))
document.write("p2.x = " + p2.x)
document.write("p2.y = " + p2.y)

On change l'objet originel

Point2.x = 999
document.write("Point2.x = " + Point2.x)
document.write("p2.x = " + p2.x)

On change la copie

p2.x = 888
document.write("Point2.x = " + Point2.x)

Dans les deux cas, la modification n'affecte que l'objet modifié et pas la copie comme avec l'avatar ou l'original, comme avec la référence.

Si l'objet à dupliquer est de type clé-valeur, sans objet imbriqué, une simple boucle permet aussi la copie.

var pmap = {};
for(var k in Point2) { pmap[k] = Point2[k] };
document.write("pmap.x = " + pmap.x)
document.write("pmap.y = " + pmap.y)

Ces deux solutions conviennent lorsque la performance n'est pas cruciale, sinon définir l'object comme une fonction est plus rapide.

Voir aussi

© 22 octobre 2015 Xul.fr