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.

Leggi di più...

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.

jsfiddle


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.



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;
}


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.createsoluzioni 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;
};




clone