javascript - objects - typescript object assign
Come faccio a clonare correttamente un oggetto JavaScript? (20)
Un modo elegante per clonare un oggetto Javascript in una riga di codice
Un metodo Object.assign
fa parte dello standard ECMAScript 2015 (ES6) e fa esattamente ciò di cui hai bisogno.
var clone = Object.assign({}, obj);
Il metodo Object.assign () viene utilizzato per copiare i valori di tutte le proprietà enumerabili di uno o più oggetti di origine in un oggetto di destinazione.
Il polyfill per supportare i browser più vecchi:
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;
}
});
}
Ho un oggetto, x
. Mi piacerebbe copiarlo come oggetto y
, tale che le modifiche a y
non modificano x
. Mi sono reso conto che copiare oggetti derivati da oggetti JavaScript incorporati comporta proprietà extra indesiderate. Questo non è un problema, dal momento che sto copiando uno dei miei oggetti letterali.
Come faccio a clonare correttamente un oggetto JavaScript?
Ci sono diversi problemi con la maggior parte delle soluzioni su Internet. Così ho deciso di fare un follow-up, che include, perché la risposta accettata non dovrebbe essere accettata.
situazione di partenza
Voglio copiare in profondità un Object
Javascript con tutti i suoi figli e i loro figli e così via. Ma dal momento che non sono un normale sviluppatore, il mio Object
ha properties
normali , circular structures
e persino nested objects
.
Quindi, prima creiamo una circular structure
e un nested object
.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Portiamo tutto insieme in un Object
chiamato a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Successivamente, vogliamo copiare a
in una variabile denominata b
e mutarla.
var b = a;
b.x = 'b';
b.nested.y = 'b';
Sai cosa è successo qui perché se no non ti atterri nemmeno su questa grande domanda.
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"
}
}
Ora troviamo una soluzione.
JSON
Il primo tentativo che ho provato è stato usare JSON
.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
Non sprecare troppo tempo su di esso, riceverai TypeError: Converting circular structure to JSON
.
Copia ricorsiva (la "risposta" accettata)
Diamo un'occhiata alla risposta accettata.
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.");
}
Sembra buono, eh? È una copia ricorsiva dell'oggetto e gestisce anche altri tipi, come Date
, ma non era un requisito.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
La ricorsione e circular structures
non funzionano bene insieme ... RangeError: Maximum call stack size exceeded
soluzione nativa
Dopo aver discusso con il mio collega, il mio capo ci ha chiesto cosa è successo, e ha trovato una soluzione semplice dopo alcuni googling. Si chiama Object.create
.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
Questa soluzione è stata aggiunta a Javascript qualche tempo fa e persino gestisce 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"
}
}
... e vedi, non funzionava con la struttura annidata all'interno.
polyfill per la soluzione nativa
C'è un polyfill per Object.create
nel browser più vecchio proprio come l'IE 8. È simile a quello raccomandato da Mozilla e, naturalmente, non è perfetto e comporta lo stesso problema della soluzione nativa .
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
Ho messo F
fuori dal campo di applicazione in modo da poter dare un'occhiata a ciò che instanceof
ci dice.
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
Lo stesso problema della soluzione nativa , ma un po 'peggio dell'output.
la migliore (ma non perfetta) soluzione
Quando ho scavato, ho trovato una domanda simile ( in Javascript, quando eseguo una copia profonda, come posso evitare un ciclo, a causa di una proprietà che è "questa"? ) A questa, ma con una soluzione migliore.
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';
E diamo un'occhiata all'output ...
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
I requisiti sono abbinati, ma ci sono ancora alcuni problemi minori, tra cui la modifica instance
di nested
e circ
in Object
.
La struttura degli alberi che condividono una foglia non verrà copiata, diventeranno due foglie indipendenti:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
conclusione
L'ultima soluzione che utilizza la ricorsione e una cache potrebbe non essere la migliore, ma è una vera copia profonda dell'oggetto. Gestisce properties
semplici, circular structures
e nested object
, ma rovinerà l'istanza di essi durante la clonazione.
Con jQuery, puoi copiare in modo superficiale con extend :
var copiedObject = jQuery.extend({}, originalObject)
le successive modifiche a copyObject non avranno effetto su originalObject e viceversa.
O per fare una copia profonda :
var copiedObject = jQuery.extend(true, {}, originalObject)
Da questo articolo: Come copiare matrici e oggetti in Javascript di 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;
};
In ECMAScript 6 esiste il metodo Object.assign , che copia i valori di tutte le proprietà enumerabili da un oggetto a un altro. Per esempio:
var x = {myProp: "value"};
var y = Object.assign({}, x);
Ma tieni presente che gli oggetti nidificati vengono comunque copiati come riferimento.
In ES-6 puoi semplicemente usare Object.assign (...). Ex:
let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);
Un buon riferimento è qui: https://googlechrome.github.io/samples/object-assign-es6/
Per MDN :
- Se vuoi una copia superficiale, usa
Object.assign({}, a)
- Per la copia "profonda", usa
JSON.parse(JSON.stringify(a))
Non è necessario per le librerie esterne ma è necessario prima verificare la compatibilità del browser .
Per coloro che utilizzano AngularJS, esiste anche un metodo diretto per la clonazione o l'estensione degli oggetti in questa libreria.
var destination = angular.copy(source);
o
angular.copy(source, destination);
Altro nella documentation angular.copy ...
Se non si utilizzano le funzioni all'interno del proprio oggetto, una fodera molto semplice può essere la seguente:
var cloneOfA = JSON.parse(JSON.stringify(a));
Questo funziona per tutti i tipi di oggetti contenenti oggetti, matrici, stringhe, booleani e numeri.
Vedi anche questo articolo sull'algoritmo clone strutturato dei browser che viene utilizzato quando si inviano messaggi da e verso un lavoratore. Contiene anche una funzione per la clonazione profonda.
Se stai bene con una copia superficiale, la libreria underscore.js ha un metodo clone .
y = _.clone(x);
oppure puoi estenderlo come
copiedObject = _.extend({},originalObject);
OK, immagina di avere questo oggetto qui sotto e vuoi clonarlo:
let obj = {a:1, b:2, c:3}; //ES6
o
var obj = {a:1, b:2, c:3}; //ES5
La risposta è principalmente depenata su quale ECMAscript stai usando, in ES6+
, puoi semplicemente usare Object.assign
per fare il clone:
let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};
o usando un operatore di spread come questo:
let cloned = {...obj}; //new {a:1, b:2, c:3};
Ma se usi ES5
, puoi usare pochi metodi, ma JSON.stringify
, assicurati di non usare per una grande quantità di dati da copiare, ma in molti casi potrebbe essere un modo pratico, qualcosa del genere:
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
Ecco una funzione che puoi usare.
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;
}
Interessato alla clonazione di oggetti semplici:
JSON.parse (JSON.stringify (json_original));
Fonte: come copiare l'oggetto JavaScript nella nuova variabile NON come riferimento?
Usando Lodash:
var y = _.clone(x, true);
Consultare http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data per l'algoritmo del "Passaggio sicuro di dati strutturati" del W3C, destinato ad essere implementato dai browser per il passaggio di dati ad es. agli impiegati del web. Tuttavia, ha alcune limitazioni, in quanto non gestisce le funzioni. Vedi https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm per ulteriori informazioni, incluso un algoritmo alternativo in JS che ti fa diventare parte del modo in cui ti trovi.
Ho scritto la mia implementazione. Non sono sicuro se conta come una soluzione migliore:
/*
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) /
*/
Di seguito è l'implementazione:
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;
}
Nuova risposta a una vecchia domanda! Se hai il piacere di usare ECMAScript 2016 (ES6) con Spread Syntax , è facile.
keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}
Ciò fornisce un metodo pulito per una copia superficiale di un oggetto. Fare una copia approfondita, significare makign una nuova copia di ogni valore in ogni oggetto annidato ricorsivamente, richiede una delle soluzioni più pesanti sopra.
JavaScript continua ad evolversi.
Poiché lo mindeavor affermato che l'oggetto da clonare è un oggetto "costruito letteralmente", una soluzione potrebbe essere semplicemente generare l'oggetto più volte anziché clonare un'istanza dell'oggetto:
function createMyObject()
{
var myObject =
{
...
};
return myObject;
}
var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
Volevo solo aggiungere a tutte le Object.create
soluzioni in questo post, che questo non funziona nel modo desiderato con nodejs.
In Firefox il risultato di
var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´
è
{test:"test"}
.
In nodejs lo è
{}
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;
};