JavaScript: Comment passer objet par valeur?


96
  • Lors de la transmission d'objets en tant que paramètres, JavaScript les transmet par référence et rend difficile la création de copies locales des objets.

    var o = {};
    (function(x){
        var obj = x;
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    oaura .fooet .bar.

  • Il est possible de contourner ce problème par le clonage; exemple simple:

    var o = {};
    
    function Clone(x) {
       for(p in x)
       this[p] = (typeof(x[p]) == 'object')? new Clone(x[p]) : x[p];
    }
    
    (function(x){
        var obj = new Clone(x);
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    on'aura pas .fooou .bar.


Question

  1. Existe-t-il un meilleur moyen de transmettre des objets par valeur, autre que de créer une copie / clonage locale?

3
Quel est votre cas d'utilisation pour avoir besoin de cela?
jondavidjohn le

2
Programmation amusante. Voir si les nouveaux moteurs JS ont résolu cela (techniquement, c'est passer la référence par valeur), mais surtout pour le plaisir.
vol7ron le

1
Voir JavaScript est-il un langage pass-by-reference ou pass-by-value? - JavaScript ne passe pas par référence. Comme Java, lors du passage d'objets à une fonction, il passe par valeur, mais la valeur est une référence. Voir aussi Java passe-t-il par référence? .
Richard JP Le Guen

1
Autant que je sache, vous ne pouvez pas passer d'objets par valeur. Même si c'était le cas, cela ferait en fait le clonage dont vous parlez ci-dessus, donc il n'y a rien à gagner que je puisse voir. Autre que peut-être sauver 3 lignes de code.
Thor84no

1
@ vol7ron Oui, ils l'ont résolu en implémentant correctement une caractéristique de conception de langage.
jondavidjohn le

Réponses:


61

Pas vraiment.

Selon ce dont vous avez réellement besoin, une possibilité peut être de définir ocomme prototype d'un nouvel objet.

var o = {};
(function(x){
    var obj = Object.create( x );
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o);

alert( o.foo ); // undefined

Ainsi, toutes les propriétés que vous ajoutez objne seront pas ajoutées o. Toutes les propriétés ajoutées objavec le même nom de propriété qu'une propriété dans ol'ombre de la opropriété.

Bien sûr, toutes les propriétés ajoutées à oseront disponibles à partir de objsi elles ne sont pas masquées, et tous les objets qui ont odans la chaîne de prototypes verront les mêmes mises à jour de o.

De plus, si obja une propriété qui fait référence à un autre objet, comme un tableau, vous devez vous assurer de faire de l'ombre à cet objet avant d'ajouter des membres à l'objet, sinon, ces membres seront ajoutés objet partagés entre tous les objets ont objdans la chaîne de prototypes.

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // 'new_value'

Ici , vous pouvez voir que parce que vous ne l' avez pas l' ombre du tableau à bazsur oune bazpropriété sur objle o.baztableau se modifie.

Donc, à la place, vous devez d'abord l'observer:

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz = [];
    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // undefined

3
+1, j'allais aussi suggérer Object.createdans ma réponse, mais je ne voulais pas m'étendre pour expliquer la superficialité de la copie ;-)
Andy E

+1, j'ai oublié ça. J'ai essayé var obj = new Object(x). Pour moi, je pense que new Object(x)cela fonctionnerait Object.create(x)par défaut - intéressant
vol7ron

1
@ vol7ron: Ouais, new Object(x)il recrache juste le même objet (comme vous l'avez probablement remarqué) . Ce serait bien s'il y avait une manière native de cloner. Je ne sais pas s'il y a quelque chose en cours.
user113716

43

Consultez cette réponse https://stackoverflow.com/a/5344074/746491 .

En bref, JSON.parse(JSON.stringify(obj))c'est un moyen rapide de copier vos objets, si vos objets peuvent être sérialisés en json.


2
J'ai tendance à éviter cela car les objets ne peuvent pas toujours être sérialisés et parce que dans les navigateurs d'âge moyen comme IE7 / 8, JSONN'est pas inclus et doit être défini - pourrait tout aussi bien être plus descriptif avec un nom comme Clone. Cependant, je soupçonne que mon opinion pourrait changer à l'avenir, car JSON est plus fortement intégré dans les logiciels non basés sur un navigateur comme les bases de données et les IDE
vol7ron

1
S'il n'y a pas de fonctions et d'objets de date, la solution JSON.parse semble être la meilleure. stackoverflow.com/questions/122102/…
Herr_Hansen

Vraiment?!? Sérialiser en JSON juste pour créer une copie d'objet? Encore une raison de haïr ce langage absurde.
RyanNerd

1
La sérialisation de JSON est vraiment un moyen rapide de le copier? Selon la documentation de NodeJS, la manipulation JSON est la tâche la plus gourmande en ressources processeur. nodejs.org/en/docs/guides/dont-block-the-event-loop/…
harshad

38

Voici la fonction de clonage qui effectuera une copie complète de l'objet:

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);

    return temp;
}

Vous pouvez maintenant utiliser comme ceci:

(function(x){
    var obj = clone(x);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o)

3
C'était une réponse profonde.
Nathan

Belle! Meilleure réponse IMO.
ganders

23

Utilisation Object.assign()

Exemple:

var a = {some: object};
var b = new Object;
Object.assign(b, a);
// b now equals a, but not by association.

Un exemple plus propre qui fait la même chose:

var a = {some: object};
var b = Object.assign({}, a);
// Once again, b now equals a.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


4
Mieux:var b = Object.assign({}, a);
Bergi

D'accord. Mettre à jour la réponse en conséquence. De plus, tous les navigateurs ne prennent pas en charge la méthode assign (). En fonction de vos exigences de compatibilité, l'utilisation d'un polyfill comme celui du MDN peut être nécessaire.
Brandon

3
@Brandon, dans le lien même que vous avez fourni, il donne un avertissement pour le clonage profond: "Pour le clonage profond, nous devons utiliser d'autres alternatives car Object.assign () copie les valeurs de propriété. Si la valeur source est une référence à un objet, il ne copie que cette valeur de référence. "
pwilcox

2
Cela fonctionne, mais gardez à l'esprit que cela n'effectue pas de copie profonde, les objets contenus à l'intérieur aseront copiés comme référence.
Stoinov

15

Vous êtes un peu confus sur le fonctionnement des objets en JavaScript. La référence de l'objet est la valeur de la variable. Il n'y a pas de valeur non sérialisée. Lorsque vous créez un objet, sa structure est stockée en mémoire et la variable à laquelle il a été affecté contient une référence à cette structure.

Même si ce que vous demandez était fourni dans une sorte de construction de langue native facile, il s'agirait encore techniquement de clonage.

JavaScript n'est en réalité qu'un passage par valeur ... c'est juste que la valeur transmise peut être une référence à quelque chose.


1
Hé AndyE! Je ne suis pas confus par cela, j'avais bon espoir que JS ferait le clonage par défaut. Il y a de grandes inefficacités dans l'auto-clonage, donc si le moteur le faisait, je suis sûr que ce serait mieux. Je doute que le besoin / la demande dépasse l'avantage, cependant.
vol7ron le

12
Dans JS, lorsque les gens disent par référence plutôt que par valeur, ils veulent dire que la valeur est comme un pointeur plutôt qu'un doublon. C'est un jargon assez bien établi.
Erik Reppen

4
@Erik: à mon humble avis, je pense qu'il vaut mieux le définir plus clairement pour ne pas confondre les nouveaux venus dans la langue. Ce n'est pas parce que quelque chose est bien établi qu'il est approprié ou même techniquement correct (car ce n'est pas le cas). Les gens disent «par référence» parce qu'il leur est plus facile de dire cela que d'expliquer comment cela fonctionne vraiment.
Andy E

6
La valeur est une référence à un emplacement en mémoire. En fin de compte, vous agissez toujours sur le même élément en mémoire plutôt que de travailler avec une copie. Comment rendre cette distinction ambiguë est-il censé aider les nouveaux arrivants? C'est ainsi que cela est expliqué dans de nombreux livres techniques, y compris O'Reilly's Authoritative Guide, qui en est maintenant à sa cinquième édition (OT: et très faiblement mise à jour).
Erik Reppen

1
@AndyE J'ai fait un peu plus de lecture pour voir de quoi il s'agissait. OMI, c'est un peu comme la question de savoir si JS a une "vraie" POO ou si nous pouvons correctement appeler quelque chose une "méthode" par rapport à d'autres langages. Je ne suis pas vraiment certain qu'il serait possible de traiter vars comme écrivant directement sur le même objet en mémoire plutôt que d'écraser son pointeur copié dans un langage basé sur la fermeture avec une ramification du comportement de l'opérateur d'affectation. La seule vraie distinction de comportement dans ce que nous entendons dans JS par rapport aux autres langages est dans ce qui se passe lorsque vous utilisez l'opérateur d'affectation.
Erik Reppen

15

Utilisez ceci

x = Object.create(x1);

xet x1sera deux objets différents, le changement xne changera pasx1


Je finis par utiliser Object.assign (). Pour un cas de test simple, .create () fonctionne mais pour plus complexe (dont je ne trouve pas encore le modèle), il fait toujours référence à la même adresse.
Coisox

Cela ne changera pas seulement si les valeurs de l'objet ne sont pas stockées par référence, c'est-à-dire qu'il s'agit de types de données prémitifs tels que nombre ou booléen. Par exemple, considérons le code ci-dessous a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (hache);
Jjagwe Dennis

8

Javascript passe toujours par valeur. Dans ce cas, il passe une copie de la référenceo dans la fonction anonyme. Le code utilise une copie de la référence mais il mute l'objet unique. Il n'y a aucun moyen de faire passer javascript par autre chose que la valeur.

Dans ce cas, vous souhaitez transmettre une copie de l'objet sous-jacent. Le clonage de l'objet est le seul recours. Votre méthode de clonage a cependant besoin d'un peu de mise à jour

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}

1
+1 Pour le "passe toujours par valeur" bien que je préfère utiliser l' appel par partage d'objets (par opposition à "Appel par valeur [de la référence]") et promouvoir "la valeur" vers "l'objet lui-même" (avec la note que l'objet n'est pas copié / cloné / dupliqué) car les références ne sont qu'un détail d'implémentation et ne sont pas exposées à JavaScript (ou directement mentionnées dans la spécification!).

J'ai eu un problème pour ajouter des propriétés à un objet que je reçois d'une connexion à distance (je pensais que cela pourrait être le problème que je ne suis pas en mesure d'ajouter une propriété à cet objet) alors j'ai essayé cette fonction de copie superficielle et j'ai obtenu cette erreur - Prototype d'objet peut seulement être un Object ou null à var copy = Object.create (o); toute idée comment résoudre mon problème
Harshit Laddha

N'est-ce pas une copie complète?
River Tam

@RiverTam Non, ce n'est pas une copie complète, car elle ne clone pas les objets dans les objets. Cependant, vous pouvez facilement en faire une copie complète simplement en le faisant s'appeler récursivement sur chacun des sous-objets.
ABPerson

7

Pour les utilisateurs de jQuery, il existe également un moyen de le faire de manière simple en utilisant le framework. Juste une autre façon jQuery nous facilite un peu la vie.

var oShallowCopy = jQuery.extend({}, o);
var oDeepCopy    = jQuery.extend(true, {}, o); 

références :


Ah - vous avez découvert cette vieille question :) Je pense que la question originale était plus une exploration du langage JS. Il semble que même ma terminologie était faible à l'époque puisque mon exemple était une copie profonde plutôt qu'un clone . Mais il semble que les objets / hachages ne puissent être passés que par référence, ce qui est courant dans les langages de script
vol7ron

1
:) Je suis tombé dessus à la recherche de la fonctionnalité exacte. J'avais un objet de données que je devais copier pour le modifier avant de le soumettre tout en conservant l'objet d'origine. Je préfère la solution JS native et je vais l'utiliser dans ma propre bibliothèque de base, mais la solution jQuery est ma solution temporaire. La réponse acceptée est fantastique. : D
Brett Weber

6

En fait, Javascript est toujours passé par valeur . Mais comme les références d'objet sont des valeurs , les objets se comportent comme s'ils sont passés par référence .

Ainsi, afin de se promener ce, stringify l'objet et analyser en arrière, à la fois en utilisant JSON. Voir l'exemple de code ci-dessous:

var person = { Name: 'John', Age: '21', Gender: 'Male' };

var holder = JSON.stringify(person);
// value of holder is "{"Name":"John","Age":"21","Gender":"Male"}"
// note that holder is a new string object

var person_copy = JSON.parse(holder);
// value of person_copy is { Name: 'John', Age: '21', Gender: 'Male' };
// person and person_copy now have the same properties and data
// but are referencing two different objects

2
Oui, c'est une option pour une simple conversion / re-conversion. Généralement, travailler avec des chaînes est très inefficace; cependant, je n'ai pas évalué les performances de l'analyse JSON depuis un certain temps. Gardez à l'esprit que même si cela fonctionne pour des objets simples, les objets contenant des fonctions comme valeurs perdront du contenu.
vol7ron

4

use obj2 = {... obj1} Les deux objets ont maintenant la même valeur et une référence différente


2
Nouveau sur javascript ici. L'opérateur de propagation crée une nouvelle valeur au lieu de copier par référence? Alors pourquoi a-t-il été décliné?
jo3birdtalk

2
Au premier coup d'œil, cela fonctionne pour moi. Mais je découvre que ce n'est qu'à la première couche de la variable. Par exemple, cela détermine quand let a = {value: "old"} let b = [...a] b.value = "new", et a sera {value: "old"}, b sera {value: "new"}. Mais cela ne fonctionne pas quand let a = [{value: "old"}] let b = [...a] b[0].value = "new", et a sera [{value: "new"}], b sera [{value: "new"}].
Brady Huang

oui, il fait une copie superficielle. Si vous avez un objet imbriqué, vous devez effectuer une copie complète. Pour plus de simplicité, vous pouvez utiliser les fonctions clone et deepClone de lodash
Geetanshu Gulati

3

J'avais besoin de copier un objet par valeur (pas par référence) et j'ai trouvé cette page utile:

Quelle est la manière la plus efficace de cloner en profondeur un objet en JavaScript? . En particulier, cloner un objet avec le code suivant par John Resig:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

La question concerne JavaScript, pas la bibliothèque jQuery.
j08691

1
C'est vrai, pour les personnes qui ne peuvent pas utiliser Jquery pour une raison quelconque, cela ne les aiderait pas. Cependant, comme Jquery est une bibliothèque javascript, je pense que cela aidera toujours la plupart des utilisateurs de javascript.
Anthony Haffey

Cela n'aidera aucun des programmeurs à essayer de le faire sur le serveur. Donc non. Ce n'est pas une réponse valable.
Tomasz Gałkowski

3

Avec la syntaxe ES6:

let obj = Object.assign({}, o);


3
Très bonne mention @galkowskit. Le plus gros problème (mieux: chose à noter ) avec le Object.assignest qu'il n'effectue pas de copie profonde.
vol7ron

1

En résumé, c'est juste un proxy sophistiqué trop compliqué, mais peut - être que Catch-All Proxies pourrait le faire?

var o = {
    a: 'a',
    b: 'b',
    func: function() { return 'func'; }
};

var proxy = Proxy.create(handlerMaker(o), o);

(function(x){
    var obj = x;
    console.log(x.a);
    console.log(x.b);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(proxy);

console.log(o.foo);

function handlerMaker(obj) {
  return {
   getOwnPropertyDescriptor: function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name);
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getPropertyDescriptor:  function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name); // not in ES5
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getOwnPropertyNames: function() {
     return Object.getOwnPropertyNames(obj);
   },
   getPropertyNames: function() {
     return Object.getPropertyNames(obj);                // not in ES5
   },
   defineProperty: function(name, desc) {

   },
   delete:       function(name) { return delete obj[name]; },   
   fix:          function() {}
  };
}

Richard, je suis un peu en retard pour répondre :) mais je pense que je ne l'ai jamais fait pour plusieurs raisons: je cherchais dans le sucre syntaxique qui donnait à JS la capacité de passer des objets de différentes manières; votre réponse a semblé longue, et j'ai été raccroché à votre utilisation de "proxy". 3 ans plus tard et je suis toujours accroché à l'utilisation. Peut-être que je n'ai pas accordé suffisamment d'attention en classe, mais comme le réseautage, je pensais que les mandataires dans CS sont censés servir d'intermédiaire. Peut-être que c'est ici aussi et je ne pense pas correctement à ce que c'est une médiation entre.
vol7ron

0

Si vous utilisez lodash ou npm, utilisez la fonction de fusion de lodash pour copier en profondeur toutes les propriétés de l'objet dans un nouvel objet vide comme ceci:

var objectCopy = lodash.merge({}, originalObject);

https://lodash.com/docs#merge

https://www.npmjs.com/package/lodash.merge


Une copie profonde doit également inclure des événements, je ne suis pas trop familier avec lodash, est-ce que cela reflète les événements sur un nouvel objet?
vol7ron
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.