J'ai un objet, x
. J'aimerais le copier en tant qu'objet y
, de sorte que les modifications apportées à y
ne modifient pas x
. Je me suis rendu compte que la copie d'objets dérivés d'objets JavaScript intégrés entraînerait des propriétés supplémentaires indésirables. Ce n'est pas un problème, car je copie l'un de mes propres objets, construits littéralement.
Comment cloner correctement un objet JavaScript?
Si vous n'utilisez pas Date
s, des fonctions indéfinies ou Infinity dans votre objet, une ligne très simple est JSON.parse(JSON.stringify(object))
:
const a = {
string: 'string',
number: 123,
bool: false,
nul: null,
date: new Date(), // stringified
undef: undefined, // lost
inf: Infinity, // forced to 'null'
}
console.log(a);
console.log(typeof a.date); // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date); // result of .toISOString()
Cela fonctionne pour tout type d'objets contenant des objets, des tableaux, des chaînes, des booléens et des nombres.
Voir aussi cet article sur l'algorithme de clone structuré des navigateurs qui est utilisé lors de la publication de messages à destination et en provenance d'un opérateur. Il contient également une fonction pour le clonage en profondeur.
Avec jQuery, vous pouvez une copie superficielle avec extend :
var copiedObject = jQuery.extend({}, originalObject)
les modifications ultérieures apportées à la copiedObject
n'affecteront pas la originalObject
, et inversement.
Ou pour faire une copie complète :
var copiedObject = jQuery.extend(true, {}, originalObject)
Dans ECMAScript 6, il existe une méthode Object.assign , qui copie les valeurs de toutes les propriétés propres énumérables d'un objet à un autre. Par exemple:
var x = {myProp: "value"};
var y = Object.assign({}, x);
Mais sachez que les objets imbriqués sont toujours copiés comme référence.
Par MDN :
Object.assign({}, a)
JSON.parse(JSON.stringify(a))
Il n’est pas nécessaire de recourir à des bibliothèques externes, mais vous devez vérifier compatibilité du navigateur en premier .
Il y a beaucoup de réponses, mais aucune qui ne mentionne Object.create de ECMAScript 5, qui, certes, ne vous donne pas une copie exacte, mais définit la source comme prototype du nouvel objet.
Donc, ce n'est pas une réponse exacte à la question, mais c'est une solution en une ligne et donc élégante. Et cela fonctionne mieux pour 2 cas:
Exemple:
var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property
Pourquoi est-ce que je considère que cette solution est supérieure? C'est natif, donc pas de boucle, pas de récursivité. Cependant, les anciens navigateurs auront besoin d'un polyfill.
Une méthode Object.assign
fait partie de la norme ECMAScript 2015 (ES6) et fait exactement ce dont vous avez besoin.
var clone = Object.assign({}, obj);
La méthode Object.assign () est utilisée pour copier les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets source vers un objet cible.
Le polyfill pour supporter les navigateurs plus anciens:
if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
enumerable: false,
configurable: true,
writable: true,
value: function(target) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert first argument to object');
}
var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) {
continue;
}
nextSource = Object(nextSource);
var keysArray = Object.keys(nextSource);
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
to[nextKey] = nextSource[nextKey];
}
}
}
return to;
}
});
}
La plupart des solutions sur Internet posent plusieurs problèmes. J'ai donc décidé de faire un suivi, y compris, pourquoi la réponse acceptée ne devrait pas être acceptée.
Je veux copier en profondeur un Javascript Object
avec tous ses enfants et leurs enfants, etc. Mais comme je ne suis pas une sorte de développeur normal, mon Object
a normal properties
, circular structures
et même nested objects
.
Créons donc un circular structure
et un nested object
en premier.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Rassemblons tout dans un Object
nommé a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Ensuite, nous voulons copier a
dans une variable nommée b
et le muter.
var b = a;
b.x = 'b';
b.nested.y = 'b';
Vous savez ce qui s’est passé ici car sinon, vous ne pourriez même pas vous poser la question.
console.log(a, b);
a --> Object {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
b --> Object {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
Maintenant, trouvons une solution.
La première tentative que j'ai essayée utilisait JSON
.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
Ne perdez pas trop de temps dessus, vous obtiendrez TypeError: Converting circular structure to JSON
.
Jetons un coup d'oeil à la réponse acceptée.
function cloneSO(obj) {
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = cloneSO(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
var copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
Ça a l'air bien, hein? C'est une copie récursive de l'objet et gère également d'autres types, comme Date
, mais ce n'était pas une obligation.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
La récursivité et circular structures
ne fonctionnent pas bien ensemble ... RangeError: Maximum call stack size exceeded
Après s'être disputé avec mon collègue, mon patron nous a demandé ce qui s'était passé et il a trouvé une solution simple après une recherche sur Google. Cela s'appelle Object.create
.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
Cette solution a été ajoutée à Javascript il y a quelque temps et gère même circular structure
.
console.log(a, b);
a --> Object {
x: "a",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
b --> Object {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
... et vous voyez, cela ne fonctionnait pas avec la structure imbriquée à l'intérieur.
Il y a un polyfill pour Object.create
dans l'ancien navigateur, tout comme le IE 8. C'est quelque chose qui est recommandé par Mozilla, et bien sûr, ce n'est pas parfait et pose le même problème que le solution native .
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
J'ai mis F
hors du champ d'application afin que nous puissions examiner ce que instanceof
nous dit.
console.log(a, b);
a --> Object {
x: "a",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
b --> F {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
console.log(typeof a, typeof b);
a --> object
b --> object
console.log(a instanceof Object, b instanceof Object);
a --> true
b --> true
console.log(a instanceof F, b instanceof F);
a --> false
b --> true
Même problème que la solution native , mais un résultat un peu moins bon.
En fouillant, j'ai trouvé une question similaire ( En Javascript, lors de la copie en profondeur, comment puis-je éviter un cycle, car une propriété est "this"? ) à celle-ci, mais avec un solution bien meilleure.
function cloneDR(o) {
const gdcc = "__getDeepCircularCopy__";
if (o !== Object(o)) {
return o; // primitive value
}
var set = gdcc in o,
cache = o[gdcc],
result;
if (set && typeof cache == "function") {
return cache();
}
// else
o[gdcc] = function() { return result; }; // overwrite
if (o instanceof Array) {
result = [];
for (var i=0; i<o.length; i++) {
result[i] = cloneDR(o[i]);
}
} else {
result = {};
for (var prop in o)
if (prop != gdcc)
result[prop] = cloneDR(o[prop]);
else if (set)
result[prop] = cloneDR(cache);
}
if (set) {
o[gdcc] = cache; // reset
} else {
delete o[gdcc]; // unset again
}
return result;
}
var b = cloneDR(a);
b.x = 'b';
b.nested.y = 'b';
Et regardons la sortie ...
console.log(a, b);
a --> Object {
x: "a",
circ: Object {
me: Object { ... }
},
nested: Object {
y: "a"
}
}
b --> Object {
x: "b",
circ: Object {
me: Object { ... }
},
nested: Object {
y: "b"
}
}
console.log(typeof a, typeof b);
a --> object
b --> object
console.log(a instanceof Object, b instanceof Object);
a --> true
b --> true
console.log(a instanceof F, b instanceof F);
a --> false
b --> false
Les exigences correspondent, mais il reste quelques problèmes mineurs, notamment la modification de instance
sur nested
et circ
en Object
.
La structure des arbres qui partagent une feuille ne sera pas copiée, ils deviendront deux feuilles indépendantes:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
La dernière solution utilisant la récursivité et un cache n'est peut-être pas la meilleure solution, mais il s'agit d'une copie profonde réelle de l'objet. Il gère les simples properties
, circular structures
et nested object
, mais il va gâcher leur instance lors du clonage.
Si vous acceptez une copie superficielle, la bibliothèque underscore.js a une méthode clone .
y = _.clone(x);
ou vous pouvez l'étendre comme
copiedObject = _.extend({},originalObject);
OK imaginez que vous avez cet objet ci-dessous et que vous voulez le cloner:
let obj = {a:1, b:2, c:3}; //ES6
ou
var obj = {a:1, b:2, c:3}; //ES5
la réponse est principalement dépendante sur laquelle ECMAscript vous utilisez, dans ES6+
, vous pouvez simplement utiliser Object.assign
pour faire le clone:
let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};
ou en utilisant l'opérateur spread comme ceci:
let cloned = {...obj}; //new {a:1, b:2, c:3};
Mais si vous utilisez ES5
, vous pouvez utiliser quelques méthodes, mais le JSON.stringify
, assurez-vous simplement que vous ne l'utilisez pas pour copier une grande quantité de données, mais il pourrait s'agir d'une seule ligne pratique dans de nombreux cas. , quelque chose comme ça:
let cloned = JSON.parse(JSON.stringify(obj));
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over
Une solution particulièrement peu élégante consiste à utiliser le codage JSON pour créer des copies complètes d’objets n’ayant pas de méthodes membres. La méthodologie consiste à JSON encoder votre objet cible, puis en le décodant, vous obtenez la copie que vous recherchez. Vous pouvez décoder autant de fois que vous voulez en faire autant de copies que nécessaire.
Bien sûr, les fonctions n'appartenant pas à JSON, cela ne fonctionne donc que pour les objets sans méthodes membres.
Cette méthodologie était parfaite pour mon cas d'utilisation, car je stockais des blobs JSON dans un magasin de valeurs-clés, et lorsqu'ils sont exposés en tant qu'objets dans une API JavaScript, chaque objet contient en fait une copie de l'état d'origine de l'objet. peut calculer le delta après que l'appelant a muté l'objet exposé.
var object1 = {key:"value"};
var object2 = object1;
object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);
object2.key = "a change";
console.log(object1);// returns value
Vous pouvez simplement utiliser un propagation pour copier un objet sans références. Mais faites attention (voir les commentaires), la "copie" est juste au niveau le plus bas de l'objet/du tableau. Les propriétés imbriquées sont toujours des références!
Clone complet:
let x = {a: 'value1'}
let x2 = {...x}
// => mutate without references:
x2.a = 'value2'
console.log(x.a) // => 'value1'
Clone avec références au deuxième niveau:
const y = {a: {b: 'value3'}}
const y2 = {...y}
// => nested object is still a references:
y2.a.b = 'value4'
console.log(y.a.b) // => 'value4'
JavaScript ne prend pas en charge nativement les clones profonds. Utilisez une fonction utilitaire. Par exemple Ramda:
Pour ceux qui utilisent AngularJS, il existe également une méthode directe de clonage ou d’extension des objets de cette bibliothèque.
var destination = angular.copy(source);
ou
angular.copy(source, destination);
Plus dans angular.copy documentation ...
La réponse de A.Levy est presque terminée, voici ma petite contribution: il existe un moyen de gérer les références récursives, voir cette ligne
if(this[attr]==this) copy[attr] = copy;
Si l'objet est un élément XML DOM, nous devons utiliser cloneNode à la place
if(this.cloneNode) return this.cloneNode(true);
Inspiré par l'étude exhaustive de A.Levy et l'approche de prototypage de Calvin, je propose cette solution:
Object.prototype.clone = function() {
if(this.cloneNode) return this.cloneNode(true);
var copy = this instanceof Array ? [] : {};
for(var attr in this) {
if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
copy[attr] = this[attr];
else if(this[attr]==this) copy[attr] = copy;
else copy[attr] = this[attr].clone();
}
return copy;
}
Date.prototype.clone = function() {
var copy = new Date();
copy.setTime(this.getTime());
return copy;
}
Number.prototype.clone =
Boolean.prototype.clone =
String.prototype.clone = function() {
return this;
}
Voir aussi la note d'Andy Burke dans les réponses.
De cet article: Comment copier des tableaux et des objets en Javascript par Brian Huisman:
Object.prototype.clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (var i in this) {
if (i == 'clone') continue;
if (this[i] && typeof this[i] == "object") {
newObj[i] = this[i].clone();
} else newObj[i] = this[i]
} return newObj;
};
Voici une fonction que vous pouvez utiliser.
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;
}
Dans ES-6, vous pouvez simplement utiliser Object.assign (...). Ex:
let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);
Une bonne référence est ici: https://googlechrome.github.io/samples/object-assign-es6/
Dans ECMAScript 2018
let objClone = { ...obj };
Sachez que les objets imbriqués sont toujours copiés à titre de référence.
Nouvelle réponse à une vieille question! Si vous avez le plaisir d'utiliser ECMAScript 2016 (ES6) avec Syntaxe de propagation , rien de plus simple.
keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}
Cela fournit une méthode propre pour une copie superficielle d'un objet. Faire une copie profonde, c'est-à-dire créer une nouvelle copie de chaque valeur de chaque objet imbriqué de manière récursive, nécessite l'une des solutions les plus lourdes ci-dessus.
JavaScript continue d'évoluer.
Vous pouvez cloner un objet et supprimer toute référence du précédent en utilisant une seule ligne de code. Faites simplement:
var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references
obj2.text = 'moo2'; // Only updates obj2's text property
console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}
Pour les navigateurs/moteurs qui ne prennent pas actuellement en charge Object.create, vous pouvez utiliser ce polyfill:
// Polyfill Object.create if it does not exist
if (!Object.create) {
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
}
Intéressé par le clonage d'objets simples:
JSON.parse(JSON.stringify(json_original));
Source: Comment copier un objet JavaScript dans une nouvelle variable PAS par référence?
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)
La solution ES6 si vous voulez (peu profonde) cloner un instance de classe et pas seulement un objet de propriété.
Utiliser Lodash:
var y = _.clone(x, true);
Je pense qu'il existe une réponse simple et efficace. En copie profonde, il y a deux préoccupations:
Donc, je pense qu'une solution simple sera tout d'abord de sérialiser et de désérialiser puis de faire une assignation dessus pour copier les fonctions aussi.
let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);
Bien que cette question apporte de nombreuses réponses, j'espère que celle-ci vous aidera également.
Pour une copie en profondeur et un clone, JSON.stringify puis JSON.parse l'objet:
obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}
function clone(src, deep) {
var toString = Object.prototype.toString;
if(!src && typeof src != "object"){
//any non-object ( Boolean, String, Number ), null, undefined, NaN
return src;
}
//Honor native/custom clone methods
if(src.clone && toString.call(src.clone) == "[object Function]"){
return src.clone(deep);
}
//DOM Elements
if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
return src.cloneNode(deep);
}
//Date
if(toString.call(src) == "[object Date]"){
return new Date(src.getTime());
}
//RegExp
if(toString.call(src) == "[object RegExp]"){
return new RegExp(src);
}
//Function
if(toString.call(src) == "[object Function]"){
//Wrap in another method to make sure == is not true;
//Note: Huge performance issue due to closures, comment this :)
return (function(){
src.apply(this, arguments);
});
}
var ret, index;
//Array
if(toString.call(src) == "[object Array]"){
//[].slice(0) would soft clone
ret = src.slice();
if(deep){
index = ret.length;
while(index--){
ret[index] = clone(ret[index], true);
}
}
}
//Object
else {
ret = src.constructor ? new src.constructor() : {};
for (var prop in src) {
ret[prop] = deep
? clone(src[prop], true)
: src[prop];
}
}
return ret;
};
Ceci est une adaptation du code de A. Levy pour gérer également le clonage de fonctions et de références multiples/cycliques. Cela signifie que si deux propriétés de l’arborescence clonée sont des références du même objet, l’arborescence de l’objet cloné aura ces propriétés. Les propriétés pointent sur un seul et même clone de l'objet référencé. Cela résout également le cas des dépendances cycliques qui, si elles ne sont pas gérées, conduisent à une boucle infinie. La complexité de l'algorithme est O (n)
function clone(obj){
var clonedObjectsArray = [];
var originalObjectsArray = []; //used to remove the unique ids when finished
var next_objid = 0;
function objectId(obj) {
if (obj == null) return null;
if (obj.__obj_id == undefined){
obj.__obj_id = next_objid++;
originalObjectsArray[obj.__obj_id] = obj;
}
return obj.__obj_id;
}
function cloneRecursive(obj) {
if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;
// Handle Date
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0; i < obj.length; ++i) {
copy[i] = cloneRecursive(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
if (clonedObjectsArray[objectId(obj)] != undefined)
return clonedObjectsArray[objectId(obj)];
var copy;
if (obj instanceof Function)//Handle Function
copy = function(){return obj.apply(this, arguments);};
else
copy = {};
clonedObjectsArray[objectId(obj)] = copy;
for (var attr in obj)
if (attr != "__obj_id" && obj.hasOwnProperty(attr))
copy[attr] = cloneRecursive(obj[attr]);
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
var cloneObj = cloneRecursive(obj);
//remove the unique ids
for (var i = 0; i < originalObjectsArray.length; i++)
{
delete originalObjectsArray[i].__obj_id;
};
return cloneObj;
}
Quelques tests rapides
var auxobj = {
prop1 : "prop1 aux val",
prop2 : ["prop2 item1", "prop2 item2"]
};
var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;
obj.f1 = function (){
this.prop1 = "prop1 val changed by f1";
};
objclone = clone(obj);
//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));
objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));
objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
Je voulais juste ajouter à toutes les solutions Object.create
de ce post, que cela ne fonctionne pas de la manière souhaitée avec nodejs.
Dans Firefox le résultat de
var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´
est
{test:"test"}
.
Dans nodejs c'est
{}
J'ai écrit ma propre implémentation. Pas sûr que cela compte comme une meilleure solution:
/*
a function for deep cloning objects that contains other nested objects and circular structures.
objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
index (z)
|
|
|
|
|
| depth (x)
|_ _ _ _ _ _ _ _ _ _ _ _
/_/_/_/_/_/_/_/_/_/
/_/_/_/_/_/_/_/_/_/
/_/_/_/_/_/_/...../
/................./
/..... /
/ /
/------------------
object length (y) /
*/
Voici la mise en œuvre:
function deepClone(obj) {
var depth = -1;
var arr = [];
return clone(obj, arr, depth);
}
/**
*
* @param obj source object
* @param arr 3D array to store the references to objects
* @param depth depth of the current object relative to the passed 'obj'
* @returns {*}
*/
function clone(obj, arr, depth){
if (typeof obj !== "object") {
return obj;
}
var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'
var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
if(result instanceof Array){
result.length = length;
}
depth++; // depth is increased because we entered an object here
arr[depth] = []; // this is the x-axis, each index here is the depth
arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
// start the depth at current and go down, cyclic structures won't form on depths more than the current one
for(var x = depth; x >= 0; x--){
// loop only if the array at this depth and length already have elements
if(arr[x][length]){
for(var index = 0; index < arr[x][length].length; index++){
if(obj === arr[x][length][index]){
return obj;
}
}
}
}
arr[depth][length].Push(obj); // store the object in the array at the current depth and length
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
}
return result;
}
(Ce qui suit était principalement une intégration de @ Maciej Bukowski , @ A. Levy , @ Jan Turoň , @ Red = = réponses, et les commentaires de @ LeviRoberts , @ RobG , merci beaucoup à eux !!!)
Copie profonde ? - OUI! (la plupart);
Copie peu profonde ? - NON! (sauf Proxy
).
Je souhaite sincèrement la bienvenue à tous pour tester clone()
.
De plus, defineProp()
est conçu pour facilement et rapidement (re) définir ou copier tout type de descripteur.
function clone(object) {
/*
Deep copy objects by value rather than by reference,
exception: `Proxy`
*/
const seen = new WeakMap()
return (function clone(object) {
if (object !== Object(object)) return object /*
—— Check if the object belongs to a primitive data type */
if (object instanceof Node) return object.cloneNode(true) /*
—— Clone DOM trees */
let _object // The clone of object
switch (object.constructor) {
case Object:
case Array:
_object = cloneObject(object)
break
case Date:
_object = new Date(+object)
break
case Function:
const fnStr = String(object)
_object = new Function("return " +
(/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr)
? "function " : ""
) + fnStr
)()
Object.defineProperties(_object,
Object.getOwnPropertyDescriptors(object)
)
break
default:
switch (Object.prototype.toString.call(object.constructor)) {
// // Stem from:
case "[object Function]": // `class`
case "[object Undefined]": // `Object.create(null)`
_object = cloneObject(object)
break
default: // `Proxy`
_object = object
}
}
return _object
function cloneObject(object) {
if (seen.has(object)) return seen.get(object) /*
—— Handle recursive references (circular structures) */
const _object = Array.isArray(object)
? []
: Object.create(Object.getPrototypeOf(object)) /*
—— Assign [[Prototype]] for inheritance */
seen.set(object, _object) /*
—— Make `_object` the associative mirror of `object` */
Reflect.ownKeys(object).forEach(key =>
defineProp(_object, key, { value: clone(object[key]) }, object)
)
return _object
}
})(object)
}
function defineProp(object, key, descriptor = {}, copyFrom = {}) {
const prevDesc = Object.getOwnPropertyDescriptor(object, key)
|| { configurable: true, writable: true }
, copyDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
|| { configurable: true, writable: true } // Custom…
|| {} // …or left to native default settings
const { configurable: _configurable, writable: _writable } = prevDesc
, test = _writable === undefined
? _configurable // Can redefine property
: _configurable && _writable // Can assign to property
if (!test || arguments.length <= 2) return test;
["get", "set", "value", "writable", "enumerable", "configurable"]
.forEach(k =>
descriptor[k] === undefined && (descriptor[k] = copyDesc[k])
)
const { get, set, value, writable, enumerable, configurable }
= descriptor
return Object.defineProperty(object, key, {
enumerable, configurable, ...get || set
? { get, set } // Accessor descriptor
: { value, writable } // Data descriptor
})
}
"use strict"
const obj0 = {
u: undefined,
nul: null,
t: true,
n: 9,
str1: "string",
str2: "",
sym: Symbol("symbol"),
[Symbol("e")]: Math.E,
f: {
getAccessorStr(object) {
return []
.concat(...
Object.values(Object.getOwnPropertyDescriptors(object))
.filter(desc => desc.writable === undefined)
.map(desc => Object.values(desc))
)
.filter(prop => typeof prop === "function")
.map(String)
},
f0: function f0() { },
f1: function () { },
f2: a => a / (a + 1),
f3: () => 0,
f4(params) { return param => param + params },
f5: (a, b) => ({ c = 0 } = {}) => a + b + c
},
o: {
n: 0,
o: {
f: function (...args) { }
}
},
arr: [[0], [1, 2]],
d: new Date(),
get g() { return 0 }
}
defineProp(obj0, "s", {
set(v) { this._s = v }
})
defineProp(obj0.arr, "tint", {
value: { is: "non-enumerable" }
})
obj0.arr[0].name = "nested array"
let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0) { return a + b }
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s", {
set(v) { this._s = v + 1 }
})
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - Routinely")
console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()
console.log("obj0\n ",
".arr.tint:", obj0.arr.tint, "\n ",
".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
".arr.tint:", obj1.arr.tint, "\n ",
".arr[0].name:", obj1.arr[0].name
)
console.log()
console.log("Accessor-type descriptor\n ",
"of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
"of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
"set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
" → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)
console.log("—— obj0 has not been interfered.")
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - Circular structures")
obj0.o.r = {}
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr
obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log("Clear obj0's recursion:",
obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
"obj0\n ",
".o.r:", obj0.o.r, "\n ",
".arr:", obj0.arr
)
console.log(
"obj1\n ",
".o.r:", obj1.o.r, "\n ",
".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - Classes")
class Person {
constructor(name) {
this.name = name
}
}
class Boy extends Person { }
Boy.prototype.sex = "M"
const boy0 = new Boy
boy0.hobby = { sport: "spaceflight" }
const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"
boy0.name = "one"
boy1.name = "neo"
console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)
Object.create()
| MDNObject.defineProperties()
| MDNLa réponse de Jan Turoň ci-dessus est très proche et peut être la meilleure solution pour un navigateur en raison de problèmes de compatibilité, mais elle risque d'entraîner d'étranges problèmes d'énumération. Par exemple, en exécutant:
for ( var i in someArray ) { ... }
Assigne la méthode clone () à i après une itération dans les éléments du tableau. Voici une adaptation qui évite l'énumération et fonctionne avec node.js:
Object.defineProperty( Object.prototype, "clone", {
value: function() {
if ( this.cloneNode )
{
return this.cloneNode( true );
}
var copy = this instanceof Array ? [] : {};
for( var attr in this )
{
if ( typeof this[ attr ] == "function" || this[ attr ] == null || !this[ attr ].clone )
{
copy[ attr ] = this[ attr ];
}
else if ( this[ attr ] == this )
{
copy[ attr ] = copy;
}
else
{
copy[ attr ] = this[ attr ].clone();
}
}
return copy;
}
});
Object.defineProperty( Date.prototype, "clone", {
value: function() {
var copy = new Date();
copy.setTime( this.getTime() );
return copy;
}
});
Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );
Cela évite de rendre la méthode clone () énumérable, car defineProperty () est par défaut enumerable en false.
Puisque mindeavor a déclaré que l'objet à cloner est un objet 'construit littéralement', une solution pourrait être simplement générer l'objet plusieurs fois plutôt que de cloner une instance de l'objet:
function createMyObject()
{
var myObject =
{
...
};
return myObject;
}
var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
Consultez http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data pour le "passage sécurisé de données structurées du W3C" "algorithme, destiné à être mis en œuvre par les navigateurs pour transmettre des données à des travailleurs Web, par exemple. Cependant, il a certaines limites, dans la mesure où il ne gère pas les fonctions. Voir https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm pour plus d'informations, y compris un algorithme alternatif dans JS qui vous permet d'accéder à une partie du chemin.
Ci-dessous, ma version du clonage en profondeur, couvrant les fonctions et gérant les références circulaires.
Utilisez deepcopy
à partir de npm
. Fonctionne à la fois dans le navigateur et dans node
en tant que npm module...
https://www.npmjs.com/package/deepcopy
let a = deepcopy(b)
Le standard HTML inclut un algorithme de clonage/sérialisation structuré interne pouvant créer des clones d'objets profonds. Il est toujours limité à certains types intégrés, mais en plus des quelques types pris en charge par JSON, il prend également en charge les dates, les régularisations, les cartes, les ensembles, les blobs, les listes de fichiers, les images, les tableaux fragmentés, les tableaux typés, et probablement davantage à l'avenir. . Il conserve également les références dans les données clonées, ce qui lui permet de prendre en charge des structures cycliques et récursives susceptibles de générer des erreurs pour JSON.
Le module v8
dans Node.js actuellement (à partir de Node 11) expose directement l'API de sérialisation structurée , mais cette fonctionnalité est toujours marquée comme "expérimentale", et sujet à modification ou suppression dans les versions futures. Si vous utilisez une version compatible, cloner un objet est aussi simple que:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
Les navigateurs ne fournissent pas actuellement d'interface directe pour l'algorithme de clonage structuré, mais une fonction globale structuredClone()
a été décrite dans whatwg/html # 793 sur GitHub . Tel que proposé actuellement, son utilisation dans la plupart des cas serait aussi simple que:
const clone = structuredClone(original);
À moins que cela ne soit livré, les implémentations de clones structurés des navigateurs ne sont exposées qu'indirectement.
Le moyen le moins coûteux de créer un clone structuré avec des API existantes consiste à publier les données via un port d'un MessageChannels . L'autre port émettra un événement message
avec un clone structuré du .data
attaché. Malheureusement, l’écoute de ces événements est nécessairement asynchrone et les alternatives synchrones sont moins pratiques.
class StructuredCloner {
constructor() {
this.pendingClones_ = new Map();
this.nextKey_ = 0;
const channel = new MessageChannel();
this.inPort_ = channel.port1;
this.outPort_ = channel.port2;
this.outPort_.onmessage = ({data: {key, value}}) => {
const resolve = this.pendingClones_.get(key);
resolve(value);
this.pendingClones_.delete(key);
};
this.outPort_.start();
}
cloneAsync(value) {
return new Promise(resolve => {
const key = this.nextKey_++;
this.pendingClones_.set(key, resolve);
this.inPort_.postMessage({key, value});
});
}
}
const structuredCloneAsync = window.structuredCloneAsync =
StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);
const main = async () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = await structuredCloneAsync(original);
// They're different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// They're cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// They contain equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
main();
Il n'y a pas de bonne option pour créer des clones structurés de manière synchrone. Voici quelques astuces peu pratiques à la place.
history.pushState()
et history.replaceState()
créent tous deux un clone structuré de leur premier argument et attribuent cette valeur à history.state
. Vous pouvez l'utiliser pour créer un clone structuré de tout objet comme celui-ci:
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
'use strict';
const main = () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = structuredClone(original);
// They're different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// They're cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// They contain equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
main();
Bien que synchrone, cela peut être extrêmement lent. Il entraîne tous les frais généraux associés à la manipulation de l'historique du navigateur. Si vous appelez cette méthode à plusieurs reprises, Chrome peut ne plus répondre temporairement.
Le constructeur Notification
crée un clone structuré de ses données associées. Il tente également d'afficher une notification du navigateur à l'utilisateur, mais cela échouera silencieusement sauf si vous avez demandé une autorisation de notification. Si vous avez l'autorisation à d'autres fins, nous fermerons immédiatement la notification que nous avons créée.
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
'use strict';
const main = () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = structuredClone(original);
// They're different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// They're cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// They contain equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.close();
return n.data;
};
main();
Cloner un objet basé sur un template
. Que faites-vous si vous ne voulez pas une copie exacte, mais que vous voulez la robustesse d'une sorte d'opération de clonage fiable mais que vous ne voulez que des bits clonés ou que vous voulez vous assurer que vous pouvez contrôler l'existence ou le format de chaque valeur d'attribut cloné?
Je contribue ceci parce que c'est utile pour nous et nous l'avons créé parce que nous ne pouvions pas trouver quelque chose de similaire. Vous pouvez l'utiliser pour cloner un objet basé sur un objet template
qui spécifie les attributs de l'objet que je veux cloner. Le modèle permet aux fonctions de transformer ces attributs en quelque chose de différent s'ils n'existent pas sur le serveur. objet source ou comme vous voulez gérer le clone. Si ce n'est pas utile, je suis sûr que quelqu'un peut supprimer cette réponse.
function isFunction(functionToCheck) {
var getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
function cloneObjectByTemplate(obj, tpl, cloneConstructor) {
if (typeof cloneConstructor === "undefined") {
cloneConstructor = false;
}
if (obj == null || typeof (obj) != 'object') return obj;
//if we have an array, work through it's contents and apply the template to each item...
if (Array.isArray(obj)) {
var ret = [];
for (var i = 0; i < obj.length; i++) {
ret.Push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor));
}
return ret;
}
//otherwise we have an object...
//var temp:any = {}; // obj.constructor(); // we can't call obj.constructor because TypeScript defines this, so if we are dealing with a TypeScript object it might reset values.
var temp = cloneConstructor ? new obj.constructor() : {};
for (var key in tpl) {
//if we are provided with a function to determine the value of this property, call it...
if (isFunction(tpl[key])) {
temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value
} else {
//if our object has this property...
if (obj[key] != undefined) {
if (Array.isArray(obj[key])) {
temp[key] = [];
for (var i = 0; i < obj[key].length; i++) {
temp[key].Push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor));
}
} else {
temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor);
}
}
}
}
return temp;
}
Une façon simple de l'appeler serait la suivante:
var source = {
a: "whatever",
b: {
x: "yeah",
y: "haha"
}
};
var template = {
a: true, //we want to clone "a"
b: {
x: true //we want to clone "b.x" too
}
};
var destination = cloneObjectByTemplate(source, template);
Si vous souhaitez utiliser une fonction pour vous assurer qu'un attribut est renvoyé ou pour vous assurer qu'il s'agit d'un type particulier, utilisez un modèle comme celui-ci. Au lieu d'utiliser { ID: true }
, nous fournissons une fonction qui ne fait que copier le ID attribute
de l'objet source, tout en garantissant qu'il s'agit d'un nombre même s'il n'existe pas dans l'objet source.
var template = {
ID: function (srcObj) {
if(srcObj.ID == undefined){ return -1; }
return parseInt(srcObj.ID.toString());
}
}
Les tableaux seront clonés correctement, mais si vous le souhaitez, vous pouvez également demander à votre propre fonction de gérer ces attributs individuels et de faire quelque chose de spécial comme celui-ci:
var template = {
tags: function (srcObj) {
var tags = [];
if (process.tags != undefined) {
for (var i = 0; i < process.tags.length; i++) {
tags.Push(cloneObjectByTemplate(
srcObj.tags[i],
{ a : true, b : true } //another template for each item in the array
);
}
}
return tags;
}
}
Ainsi, dans ce qui précède, notre modèle copie simplement l'attribut tags
de l'objet source s'il existe (il est supposé être un tableau), et pour chaque élément de ce tableau, la fonction clone est appelée pour le cloner individuellement en fonction sur un deuxième modèle qui copie simplement les attributs a
et b
de chacun de ces éléments de balise.
Si vous prenez des objets dans et hors du nœud et que vous voulez contrôler les attributs de ces objets qui sont clonés, il s'agit d'un excellent moyen de contrôler cela dans node.js
et le code fonctionne également dans le navigateur.
Voici un exemple d'utilisation: http://jsfiddle.net/hjchyLt1/
Vous pouvez utiliser la fermeture fonctionnelle pour obtenir tous les avantages d'une copie en profondeur, sans copie profonde. C'est un paradigme très différent, mais qui fonctionne bien. Au lieu d'essayer de copier un objet existant, utilisez simplement une fonction pour instancier un nouvel objet lorsque vous en avez besoin.
Tout d'abord, créez une fonction qui retourne un objet
function template() {
return {
values: [1, 2, 3],
nest: {x: {a: "a", b: "b"}, y: 100}
};
}
Puis créez une fonction de copie superficielle simple
function copy(a, b) {
Object.keys(b).forEach(function(key) {
a[key] = b[key];
});
}
Créez un nouvel objet et copiez-y les propriétés du modèle
var newObject = {};
copy(newObject, template());
Mais l'étape de copie ci-dessus n'est pas nécessaire. Tout ce que vous devez faire est ceci:
var newObject = template();
Maintenant que vous avez un nouvel objet, testez pour voir quelles sont ses propriétés:
console.log(Object.keys(newObject));
Cela affiche:
["values", "nest"]
Oui, ce sont les propriétés propres de newObject et non des références à des propriétés sur un autre objet. Vérifions simplement:
console.log(newObject.nest.x.b);
Cela affiche:
"b"
NewObject a acquis toutes les propriétés de l'objet modèle, mais est libre de toute chaîne de dépendance.
http://jsbin.com/ISUTIpoC/1/edit?js,console
J'ai ajouté cet exemple pour encourager le débat, alors merci d'ajouter quelques commentaires :)
Selon le Guide de Style JavaScript Airbnb avec 404 contributeurs:
Préférez l'opérateur d'étalement d'objet à Object.assign pour les objets peu profonds. Utilisez l'opérateur rest object pour obtenir un nouvel objet avec certaines propriétés omises.
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this
// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
J'aimerais également vous avertir que, même si Airbnb recommande à peine l'approche de propagation d'objet. Gardez à l'esprit que Microsoft Edge ne prend toujours pas en charge cette fonctionnalité 2018.
Copie superficielle: lodash _.clone ()
Une copie superficielle peut être réalisée en copiant simplement la référence.
let obj1 = {
a: 0,
b: {
c: 0,
e: {
f: 0
}
}
};
let obj3 = _.clone(obj1);
obj1.a = 4;
obj1.b.c = 4;
obj1.b.e.f = 100;
console.log(JSON.stringify(obj1));
//{"a":4,"b":{"c":4,"e":{"f":100}}}
console.log(JSON.stringify(obj3));
//{"a":0,"b":{"c":4,"e":{"f":100}}}
Deep Copy: lodash _.cloneDeep ()
les champs sont déréférencés: plutôt que les références aux objets en cours de copie
let obj1 = {
a: 0,
b: {
c: 0,
e: {
f: 0
}
}
};
let obj3 = _.cloneDeep(obj1);
obj1.a = 100;
obj1.b.c = 100;
obj1.b.e.f = 100;
console.log(JSON.stringify(obj1));
{"a":100,"b":{"c":100,"e":{"f":100}}}
console.log(JSON.stringify(obj3));
{"a":0,"b":{"c":0,"e":{"f":0}}}
Je pense que récurrence avec la mise en cache est le meilleur que nous puissions faire ici sans bibliothèques.
Et la sous-estimation WeakMap soulève le problème des cycles, dans lesquels le stockage de paires de références à des objets anciens et nouveaux peut nous aider à recréer assez facilement des arbres entiers.
J'ai empêché le clonage en profondeur des éléments du DOM, probablement vous ne voulez pas cloner la page entière :)
function deepCopy(object) {
const cache = new WeakMap(); // Map of old - new references
function copy(obj) {
if (typeof obj !== 'object' ||
obj === null ||
obj instanceof HTMLElement
)
return obj; // primitive value or HTMLElement
if (obj instanceof Date)
return new Date().setTime(obj.getTime());
if (obj instanceof RegExp)
return new RegExp(obj.source, obj.flags);
if (cache.has(obj))
return cache.get(obj);
const result = obj instanceof Array ? [] : {};
cache.set(obj, result); // store reference to object before the recursive starts
if (obj instanceof Array) {
for(const o of obj) {
result.Push(copy(o));
}
return result;
}
const keys = Object.keys(obj);
for (const key of keys)
result[key] = copy(obj[key]);
return result;
}
return copy(object);
}
Quelques tests:
// #1
const obj1 = { };
const obj2 = { };
obj1.obj2 = obj2;
obj2.obj1 = obj1; // Trivial circular reference
var copy = deepCopy(obj1);
copy == obj1 // false
copy.obj2 === obj1.obj2 // false
copy.obj2.obj1.obj2 // and so on - no error (correctly cloned).
// #2
const obj = { x: 0 }
const clone = deepCopy({ a: obj, b: obj });
clone.a == clone.b // true
// #3
const arr = [];
arr[0] = arr; // A little bit weird but who cares
clone = deepCopy(arr)
clone == arr // false;
clone[0][0][0][0] == clone // true;
NOTE: J'utilise des constantes pour for of loop, => operator et WeakMaps pour créer du code plus essentiel. Cette syntaxe (ES6) est prise en charge par les navigateurs actuels.
J'ai essayé ceci dans le cas d'un objet scalaire et cela fonctionne pour moi:
function binder(i) {
return function () {
return i;
};
}
a=1;
b=binder(a)(); // copy value of a into b
alert(++a); // 2
alert(b); // still 1
Cordialement.
Utilisation de par défaut (historiquement spécifique à nodejs mais maintenant utilisable depuis le navigateur grâce à JS moderne):
import defaults from 'object.defaults';
const myCopy = defaults({}, myObject);
S'il n'y a pas de dépendances circulaires dans votre objet, je suggère d'utiliser l'une des autres réponses ou méthodes de copie de jQuery , car elles semblent toutes assez efficaces.
S'il y a des dépendances circulaires (c'est-à-dire que deux sous-objets sont liés l'un à l'autre), vous êtes un peu foutu comme il y en a (d'un point de vue théorique) aucun moyen de résoudre ce problème avec élégance .
Ok, cela pourrait être la meilleure option pour la copie superficielle. Si suit les nombreux exemples utilisant assign, mais conserve également l'héritage et le prototype. C’est aussi simple et fonctionne pour la plupart des objets et tableaux de type tableau, à l’exception de ceux nécessitant des constructeurs ou des propriétés en lecture seule. Mais cela signifie qu’il échoue lamentablement pour les versions de primitives TypedArrays, RegExp, Date, Maps, Sets et Object (Boolean, String, etc.).
function copy ( a ) { return Object.assign( new a.constructor, a ) }
Où a
peut être n'importe quelle instance construite d'objet ou de classe, mais là encore, ne pas fiable pour les objets qui utilisent des accesseurs/gabarisseurs spécialisés ou qui ont des exigences de constructeur, mais pour des situations plus simples, cela bascule. Cela fonctionne également sur les arguments.
Vous pouvez également l'appliquer à des primitives pour obtenir des résultats étranges, mais alors ... sauf si cela finit par être un bidouillage utile, peu importe.
résultats des objets et tableaux intégrés de base ...
> a = { a: 'A', b: 'B', c: 'C', d: 'D' }
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> b = copy( a )
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> a = [1,2,3,4]
[ 1, 2, 3, 4 ]
> b = copy( a )
[ 1, 2, 3, 4 ]
Et échoue à cause des méchants get/setters, des arguments obligatoires du constructeur ou des propriétés en lecture seule, et des péchés contre le père.
> a = /\w+/g
/\w+/g
> b = copy( a ) // fails because source and flags are read-only
/(?:)/
> a = new Date ( '1/1/2001' )
2000-12-31T16:00:00.000Z
> b = copy( a ) // fails because Date using methods to get and set things
2017-02-04T14:44:13.990Z
> a = new Boolean( true )
[Boolean: true]
> b = copy( a ) // fails because of of sins against the father
[Boolean: false]
> a = new Number( 37 )
[Number: 37]
> b = copy( a ) // fails because of of sins against the father
[Number: 0]
> a = new String( 'four score and seven years ago our four fathers' )
[String: 'four score and seven years ago our four fathers']
> b = copy( a ) // fails because of of sins against the father
{ [String: ''] '0': 'f', '1': 'o', '2': 'u', '3': 'r', '4': ' ', '5': 's', '6': 'c', '7': 'o', '8': 'r', '9': 'e', '10': ' ', '11': 'a', '12': 'n', '13': 'd', '14': ' ', '15': 's', '16': 'e', '17': 'v', '18': 'e', '19': 'n', '20': ' ', '21': 'y', '22': 'e', '23': 'a', '24': 'r', '25': 's', '26': ' ', '27': 'a', '28': 'g', '29': 'o', '30': ' ', '31': 'o', '32': 'u', '33': 'r', '34': ' ', '35': 'f', '36': 'o', '37': 'u', '38': 'r', '39': ' ', '40': 'f', '41': 'a', '42': 't', '43': 'h', '44': 'e', '45': 'r', '46': 's' }
Voici une solution moderne qui ne présente pas les pièges de Object.assign()
(ne copie pas par référence):
const cloneObj = (obj) => {
return Object.keys(obj).reduce((dolly, key) => {
dolly[key] = (obj[key].constructor === Object) ?
cloneObj(obj[key]) :
obj[key];
return dolly;
}, {});
};
La méthode
Object.assign()
est utilisée pour copier les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets source vers un objet cible. Il retournera l'objet cible.
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }
Syntaxe
Object.assign(target, ...sources)
La méthode Object.assign()
ne copie que les propriétés énumérables et propres d'un objet source vers un objet cible. Il utilise [[Get]]
sur la source et [[Set]] sur la cible,
il invoquera donc les accesseurs et les setters. Par conséquent, il attribue des propriétés au lieu de copier ou de définir de nouvelles propriétés. Cela peut empêcher la fusion de nouvelles propriétés dans un prototype si les sources de fusion contiennent des getters. Pour copier les définitions de propriété, y compris leur énumération, dans les prototypes, Object.getOwnPropertyDescriptor()
et Object.defineProperty()
doivent être utilisés à la place.
Les propriétés String et Symbol sont copiées.
En cas d'erreur, par exemple, si une propriété n'est pas accessible en écriture, une erreur TypeError est générée et l'objet cible peut être modifié si des propriétés sont ajoutées avant qu'une erreur ne soit générée.
Notez que Object.assign()
ne jette pas sur les valeurs source null
ou undefined
.
Pour permettre une meilleure compréhension de la copie d’objets, ce jsbin illustratif peut être utile
class base {
get under(){return true}
}
class a extends base {}
const b = {
get b1(){return true},
b: true
}
console.log('Object assign')
let t1 = Object.create(b)
t1.x = true
const c = Object.assign(t1, new a())
console.log(c.b1 ? 'prop value copied': 'prop value gone')
console.log(c.x ? 'assigned value copied': 'assigned value gone')
console.log(c.under ? 'inheritance ok': 'inheritance gone')
console.log(c.b1 ? 'get value unchanged' : 'get value lost')
c.b1 = false
console.log(c.b1? 'get unchanged' : 'get lost')
console.log('-----------------------------------')
console.log('Object assign - order swopped')
t1 = Object.create(b)
t1.x = true
const d = Object.assign(new a(), t1)
console.log(d.b1 ? 'prop value copied': 'prop value gone')
console.log(d.x ? 'assigned value copied': 'assigned value gone')
console.log(d.under ? 'inheritance n/a': 'inheritance gone')
console.log(d.b1 ? 'get value copied' : 'get value lost')
d.b1 = false
console.log(d.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e = { ...t1, ...t2 }
console.log(e.b1 ? 'prop value copied': 'prop value gone')
console.log(e.x ? 'assigned value copied': 'assigned value gone')
console.log(e.under ? 'inheritance ok': 'inheritance gone')
console.log(e.b1 ? 'get value copied' : 'get value lost')
e.b1 = false
console.log(e.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator on getPrototypeOf')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e1 = { ...Object.getPrototypeOf(t1), ...Object.getPrototypeOf(t2) }
console.log(e1.b1 ? 'prop value copied': 'prop value gone')
console.log(e1.x ? 'assigned value copied': 'assigned value gone')
console.log(e1.under ? 'inheritance ok': 'inheritance gone')
console.log(e1.b1 ? 'get value copied' : 'get value lost')
e1.b1 = false
console.log(e1.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('keys, defineProperty, getOwnPropertyDescriptor')
f = Object.create(b)
t2 = new a()
f.x = 'a'
Object.keys(t2).forEach(key=> {
Object.defineProperty(f,key,Object.getOwnPropertyDescriptor(t2, key))
})
console.log(f.b1 ? 'prop value copied': 'prop value gone')
console.log(f.x ? 'assigned value copied': 'assigned value gone')
console.log(f.under ? 'inheritance ok': 'inheritance gone')
console.log(f.b1 ? 'get value copied' : 'get value lost')
f.b1 = false
console.log(f.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('defineProperties, getOwnPropertyDescriptors')
let g = Object.create(b)
t2 = new a()
g.x = 'a'
Object.defineProperties(g,Object.getOwnPropertyDescriptors(t2))
console.log(g.b1 ? 'prop value copied': 'prop value gone')
console.log(g.x ? 'assigned value copied': 'assigned value gone')
console.log(g.under ? 'inheritance ok': 'inheritance gone')
console.log(g.b1 ? 'get value copied' : 'get value lost')
g.b1 = false
console.log(g.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
Dans mon code, je définis fréquemment une function (_)
pour gérer les copies afin de pouvoir passer by value
aux fonctions. Ce code crée une copie complète mais conserve l'héritage. Il assure également le suivi des sous-copies afin que les objets autoréférentiels puissent être copiés sans boucle infinie. Sentez-vous libre de l'utiliser.
Ce n'est peut-être pas le plus élégant, mais il ne m'a pas encore manqué.
_ = function(oReferance) {
var aReferances = new Array();
var getPrototypeOf = function(oObject) {
if(typeof(Object.getPrototypeOf)!=="undefined") return Object.getPrototypeOf(oObject);
var oTest = new Object();
if(typeof(oObject.__proto__)!=="undefined"&&typeof(oTest.__proto__)!=="undefined"&&oTest.__proto__===Object.prototype) return oObject.__proto__;
if(typeof(oObject.constructor)!=="undefined"&&typeof(oTest.constructor)!=="undefined"&&oTest.constructor===Object&&typeof(oObject.constructor.prototype)!=="undefined") return oObject.constructor.prototype;
return Object.prototype;
};
var recursiveCopy = function(oSource) {
if(typeof(oSource)!=="object") return oSource;
if(oSource===null) return null;
for(var i=0;i<aReferances.length;i++) if(aReferances[i][0]===oSource) return aReferances[i][1];
var Copy = new Function();
Copy.prototype = getPrototypeOf(oSource);
var oCopy = new Copy();
aReferances.Push([oSource,oCopy]);
for(sPropertyName in oSource) if(oSource.hasOwnProperty(sPropertyName)) oCopy[sPropertyName] = recursiveCopy(oSource[sPropertyName]);
return oCopy;
};
return recursiveCopy(oReferance);
};
// Examples:
Wigit = function(){};
Wigit.prototype.bInThePrototype = true;
A = new Wigit();
A.nCoolNumber = 7;
B = _(A);
B.nCoolNumber = 8; // A.nCoolNumber is still 7
B.bInThePrototype // true
B instanceof Wigit // true
Le problème avec la copie d'un objet qui, éventuellement, peut pointer sur lui-même, peut être résolu avec une simple vérification. Ajoutez cette vérification chaque fois qu'il y a une action de copie. Cela peut être lent, mais cela devrait fonctionner.
J'utilise une fonction toType () pour renvoyer le type d'objet, explicitement. J'ai aussi ma propre fonction copyObj (), qui est assez similaire en logique, qui répond aux trois observations Object (), Array () et Date ().
Je le lance dans NodeJS.
NON TESTÉ, ENCORE.
// Returns true, if one of the parent's children is the target.
// This is useful, for avoiding copyObj() through an infinite loop!
function isChild(target, parent) {
if (toType(parent) == '[object Object]') {
for (var name in parent) {
var curProperty = parent[name];
// Direct child.
if (curProperty = target) return true;
// Check if target is a child of this property, and so on, recursively.
if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') {
if (isChild(target, curProperty)) return true;
}
}
} else if (toType(parent) == '[object Array]') {
for (var i=0; i < parent.length; i++) {
var curItem = parent[i];
// Direct child.
if (curItem = target) return true;
// Check if target is a child of this property, and so on, recursively.
if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') {
if (isChild(target, curItem)) return true;
}
}
}
return false; // Not the target.
}
Je ne sais pas dans quels cas cela ne fonctionne pas, mais cela m'a donné une copie d'un tableau. Je pense que c'est mignon :) J'espère que ça aide
copiedArr = origArr.filter(function(x){return true})
Pour manipuler des objets circulaires que JSON.stringify
ne peut pas gérer, vous pouvez importer une bibliothèque appelée JSOG , qui sérialise et désérialise les graphiques arbitraires au format JSON.
var clone = JSOG.parse(JSOG.stringify(original));
Il pourrait également être intéressant d’essayer d’appliquer cette astuce à JSOG pour le clonage (n’avez pas le temps, mais si quelqu'un veut tenter le coup ...):
Sérialiser une fonction simple:
foo.f = function(a) { return a }
var stringForm = foo.f.toString() // "function (a) { return a }"
Désérialiser une fonction:
eval("foo.f = " + stringForm)
Certaines conventions (probablement au nom de la propriété) pour identifier les fonctions par rapport aux chaînes régulières seraient nécessaires (@func_f
peut-être).
Bien sûr, si la fonction appelle une deuxième fonction, celle-ci devra exister exactement comme pour l'originale.
Ce qui précède est cependant assez dangereux si vous acceptez le formulaire sérialisé d'une source non fiable, mais accepter toute fonction sous une forme quelconque provenant d'une source non fiable serait dangereux. Par conséquent, si vous êtes intéressé par des fonctions de clonage, la confiance doit déjà avoir été établie. (ou vous avez déjà l'intention d'écrire une faille de sécurité!).
Avertissement: Je n'ai pas testé la vitesse de JSOG stringify/parse vs JSON stringify/parse, mais cela fonctionne sur les objets simples (circulaires) que j'ai testés. avec.
Si vous avez un objet avec des fonctions, vous pouvez le faire avec JSONfn, voir http://www.eslinstructor.net/jsonfn/ .
var obj= {
name:'Marvin',
getName : function(){
return this.name;
}
}
var cobj = JSONfn.parse(JSONfn.stringify(obj));
Vous pouvez cloner votre objet sans modification objet parent -
/** [Object Extend]*/
( typeof Object.extend === 'function' ? undefined : ( Object.extend = function ( destination, source ) {
for ( var property in source )
destination[property] = source[property];
return destination;
} ) );
/** [/Object Extend]*/
/** [Object clone]*/
( typeof Object.clone === 'function' ? undefined : ( Object.clone = function ( object ) {
return this.extend( {}, object );
} ) );
/** [/Object clone]*/
let myObj = {
a:1, b:2, c:3, d:{
a:1, b:2, c:3
}
};
let clone = Object.clone( myObj );
clone.a = 10;
console.log('clone.a==>', clone.a); //==> 10
console.log('myObj.a==>', myObj.a); //==> 1 // object not modified here
let clone2 = Object.clone( clone );
clone2.a = 20;
console.log('clone2.a==>', clone2.a); //==> 20
console.log('clone.a==>', clone.a); //==> 10 // object not modified here
Je suis venu sur cette page à cause de la même question, mais je n'utilise ni JQuery, ni aucune des méthodes de clonage n'a fonctionné pour mes propres objets.
Je suis conscient que ma réponse n'est pas trop liée à cette question parce que c'est une approche différente. Au lieu d'utiliser des fonctions de clonage, j'utilise une fonction de création. Cela a fonctionné pour moi pour les objectifs suivants (malheureusement restrictifs):
J'ai d'abord défini mes objets comme ceci:
var obj= new Object();
obj.Type='Row';
obj.ID=1;
obj.Value='Blah blah';
Maintenant j'ai tout déplacé comme:
function getObjSelektor(id_nummer,selected){
var obj = document.createElement("select");
obj.setAttribute("id","Selektor_"+id_nummer);
obj.setAttribute("name","Selektor");
obj.setAttribute("size","1");
var obj_opt_1 = document.createElement("option");
obj_opt_1.setAttribute("value","1");
if(1==selected)
posopval_opt_1.setAttribute("selected","selected");
obj_opt_1.innerHTML="Blah blah";
obj.appendChild(obj_opt_1);
var obj_opt_2 = document.createElement("option");
obj_opt_2.setAttribute("value","2");
if(2==selected)
obj_opt_2.setAttribute("selected","selected");
obj_opt_2.innerHTML="2nd Row";
obj.appendChild(obj_opt_2);
...
return obj;
}
Et appelez la fonction dans le code normal:
myDiv.getObjSelektor(getObjSelektor(anotherObject.ID));
Comme je l'ai dit, c'est une approche différente qui a résolu mon problème pour mes besoins.
//
// creates 'clone' method on context object
//
// var
// clon = Object.clone( anyValue );
//
!((function (propertyName, definition) {
this[propertyName] = definition();
}).call(
Object,
"clone",
function () {
function isfn(fn) {
return typeof fn === "function";
}
function isobj(o) {
return o === Object(o);
}
function isarray(o) {
return Object.prototype.toString.call(o) === "[object Array]";
}
function fnclon(fn) {
return function () {
fn.apply(this, arguments);
};
}
function owns(obj, p) {
return obj.hasOwnProperty(p);
}
function isemptyobj(obj) {
for (var p in obj) {
return false;
}
return true;
}
function isObject(o) {
return Object.prototype.toString.call(o) === "[object Object]";
}
return function (input) {
if (isfn(input)) {
return fnclon(input);
} else if (isobj(input)) {
var cloned = {};
for (var p in input) {
owns(Object.prototype, p)
|| (
isfn(input[p])
&& ( cloned[p] = function () { return input[p].apply(input, arguments); } )
|| ( cloned[p] = input[p] )
);
}
if (isarray(input)) {
cloned.length = input.length;
"concat every filter forEach indexOf join lastIndexOf map pop Push reduce reduceRight reverse shift slice some sort splice toLocaleString toString unshift"
.split(" ")
.forEach(
function (methodName) {
isfn( Array.prototype[methodName] )
&& (
cloned[methodName] =
function () {
return Array.prototype[methodName].apply(cloned, arguments);
}
);
}
);
}
return isemptyobj(cloned)
? (
isObject(input)
? cloned
: input
)
: cloned;
} else {
return input;
}
};
}
));
//
Facile
var restore = { name:'charlesi',
age:9}
var prev_data ={
name: 'charles'
age : 10
}
var temp = JSON.stringify(prev_data)
restore = JSON.parse(temp)
restore = {
name:'charlie',
age : 12}
sortie prev_data:
{
name: 'charles'
age : 10
}
De la Directives de codage JavaScript Apple :
// Create an inner object with a variable x whose default
// value is 3.
function innerObj()
{
this.x = 3;
}
innerObj.prototype.clone = function() {
var temp = new innerObj();
for (myvar in this) {
// this object does not contain any objects, so
// use the lightweight copy code.
temp[myvar] = this[myvar];
}
return temp;
}
// Create an outer object with a variable y whose default
// value is 77.
function outerObj()
{
// The outer object contains an inner object. Allocate it here.
this.inner = new innerObj();
this.y = 77;
}
outerObj.prototype.clone = function() {
var temp = new outerObj();
for (myvar in this) {
if (this[myvar].clone) {
// This variable contains an object with a
// clone operator. Call it to create a copy.
temp[myvar] = this[myvar].clone();
} else {
// This variable contains a scalar value,
// a string value, or an object with no
// clone function. Assign it directly.
temp[myvar] = this[myvar];
}
}
return temp;
}
// Allocate an outer object and assign non-default values to variables in
// both the outer and inner objects.
outer = new outerObj;
outer.inner.x = 4;
outer.y = 16;
// Clone the outer object (which, in turn, clones the inner object).
newouter = outer.clone();
// Verify that both values were copied.
alert('inner x is '+newouter.inner.x); // prints 4
alert('y is '+newouter.y); // prints 16
Steve
Juste comme ce lien dit utiliser ce code:let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
Si votre objet est une classe (par exemple: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes ):
var copiedObject = jQuery.extend(true, {}, originalObject);
copiedObject.__proto__ = originalObject.__proto__;
Puis, dans copiedObject
, vous avez une instance profondément copiée de la classe originalObject
avec toutes ses méthodes.
Si vous utilisez TypeScript, devez prendre en charge les anciens navigateurs Web (et ne pouvez donc pas utiliser Object.assign
), et n'utilisez pas de bibliothèque avec une méthode de duplication intégrée, vous pouvez créer vous-même un combine
assistant dans quelques lignes de code. Il combine des objets, et si vous n'en avez qu'un, clonez-le simplement.
/** Creates a new object that combines the properties of the specified objects. */
function combine(...objs: {}[]) {
const combined = {};
objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p]));
return combined;
}
Je réponds à cette question car je ne vois aucune implémentation récursive native permettant de résoudre le problème des éléments DOM
.
Le problème est que <element>
a les attributs parent
et child
, qui sont liés aux autres éléments avec les valeurs parent
et child
, qui renvoient à l'original <element>
, provoquant soit infini récursif ou redondance cyclique.
Si votre objet est quelque chose de simple et sûr comme
{
'123':456
}
... alors toute autre réponse ici fonctionnera probablement.
Mais si vous avez ...
{
'123':<reactJSComponent>,
'456':document.createElement('div'),
}
... alors vous avez besoin de quelque chose comme ça:
// cloneVariable() : Clone variable, return null for elements or components.
var cloneVariable = function (args) {
const variable = args.variable;
if(variable === null) {
return null;
}
if(typeof(variable) === 'object') {
if(variable instanceof HTMLElement || variable.nodeType > 0) {
return null;
}
if(Array.isArray(variable)) {
var arrayclone = [];
variable.forEach((element) => {
arrayclone.Push(cloneVariable({'variable':element}));
});
return arrayclone;
}
var objectclone = {};
Object.keys(variable).forEach((field) => {
objectclone[field] = cloneVariable({'variable':variable[field]});
});
return objectclone;
}
return variable;
}