Cum fac corect clona un obiect JavaScript?

Am un obiect, "x". Am'd place să-l copieze ca obiect "y", astfel că modificările la " y "nu modifica "x". Mi-am dat seama că copierea obiectelor derivate din built-in JavaScript obiectele vor rezulta în plus, proprietăți nedorite. Asta e't o problemă, de când am'm copierea unul de-al meu, literal-obiecte construite.

Cum fac corect clona un obiect JavaScript?

Comentarii la întrebare (23)
Soluția

Pentru a face acest lucru pentru orice obiect în JavaScript nu va fi simplă sau directă. Va rula în problema în mod eronat ridica atributele de obiect's prototip care ar trebui să fie lăsat în prototip și nu copiate la noi de exemplu. Dacă, de exemplu, adăugați o "clonă" metoda de a Obiect.prototip, ca niște răspunsuri descriu, va trebui să în mod explicit sări peste acest atribut. Dar dacă există și alte metode suplimentare adăugate la Obiect.prototip, sau alte intermediar prototipuri, pe care nu't știu despre? În acest caz, veți copia atributele nu ar trebui't, astfel încât aveți nevoie pentru a detecta neprevăzute, non-locale atribute cu [hasOwnProperty`]1 metoda.

În plus față de non-nenumărate atribute, ai'll întâlni o mai greu problemă atunci când încercați să copiați obiecte care au proprietăți ascunse. De exemplu, "prototip" este o ascunse proprietate a unei funcții. De asemenea, un obiect's prototip este referit cu atributul __proto__, care este, de asemenea, ascunse, și nu va fi copiată de către un pentru/în buclă iterarea peste obiectul sursă's atribute. Cred că __proto__ ar putea fi specifice pentru Firefox's JavaScript interpret și ar putea fi ceva diferit în alte browsere, dar veți obține imaginea. Nu totul este enumerable. Puteți copia un atribut ascuns, dacă știi numele, dar eu nu't știu de orice mod de a descoperi în mod automat.

Încă un obstacol în căutarea pentru o soluție elegantă este problema înființării prototipul moștenire corect. Dacă sursa ta de obiect's prototip este "Obiect", apoi pur și simplu a crea un nou obiect general cu {} va funcționa, dar dacă sursa's prototip este un descendent al "Obiect", atunci aveți de gând să fi dispărut suplimentare membrii din acel prototip care ai lipsit de ajutorul hasOwnProperty filtru, sau care au fost în prototip, dar nu't enumerable în primul rând. O soluție ar putea fi să suni la sursă, obiect's constructor de proprietate pentru a obține copia inițială obiect și apoi copiați atributele, dar atunci tot nu va primi non-nenumărate atribute. De exemplu, o [Data`]2 obiect stochează datele sale ca o ascunse membru:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

La data de coarde pentru " d1 "va fi de 5 secunde în spatele lui "d2". O modalitate de a face o "Data" la fel ca altul este de asteptare setTime metoda, dar care este specific pentru "Data" de clasă. Eu nu't cred că există un anti-glont general soluție la această problemă, deși aș fi fericit să fi greșit!

Când a trebuit să pună în aplicare general adânc copierea am ajuns compromite presupunând că aș avea nevoie de doar pentru a copia un simplu "Obiect", "Matrice", "Data", "String", "Număr", sau "Boolean". Ultimele 3 tipuri sunt imuabile, așa că am putea face o copie superficială și nu vă faceți griji cu privire la aceasta schimbare. Am mai presupus că orice elemente conținute în "Obiect" sau "Matrice" ar fi, de asemenea, una dintre cele 6 tipuri simple în această listă. Acest lucru poate fi realizat cu cod, cum ar fi următoarele:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

Funcția de mai sus va funcționa în mod adecvat pentru 6 tipuri simple am menționat, atâta timp cât datele în obiecte și tablouri de a forma o structură de arbore. Asta este, nu e't mai mult de o referință la aceleași date în obiect. De exemplu:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

Acesta nu va fi capabil să se ocupe de orice obiect JavaScript, dar poate fi suficient pentru multe scopuri, atâta timp cât nu't se presupună că aceasta va funcționa doar pentru orice ai arunca la ea.

Comentarii (29)

Dacă nu utilizați Data, funcții, nedefinit, sau Infinit în obiect, una foarte simplă linie esteJSON.analiza(JSON.stringify(obiect))`:

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()

Aceasta funcționează pentru toate tipurile de obiecte care conțin obiecte, tablouri, siruri de caractere, boolean și numere.

A se vedea, de asemenea, acest articol despre structurat clona algoritm de browsere care este folosit la transmiterea de mesaje către și de la un lucrător. Acesta conține, de asemenea, o funcție de adâncime de clonare.

Comentarii (16)

Cu jQuery, puteți copie superficială cu prelungi:

var copiedObject = jQuery.extend({}, originalObject)

modificări ulterioare ale copiedObject nu va afecta originalObject, și vice-versa.

Sau pentru a face o copie profundă:

var copiedObject = jQuery.extend(true, {}, originalObject)
Comentarii (12)

În ECMAScript 6 există Obiect.assign metoda, care copiază valorile tuturor enumerable propriile proprietăți de la un obiect la altul. De exemplu:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

Dar să fie conștienți de faptul că, obiecte imbricate sunt încă copiate ca referință.

Comentarii (9)

Pe MDN:

  • Dacă vrei superficial copia, folosi Obiect.atribui({}, a)
  • Pentru "profund" copia, folosi JSON.analiza(JSON.stringify(a))

Nu este nevoie de biblioteci externe, dar ai nevoie pentru a verifica compatibilitate browser-ul prima.

Comentarii (5)

Există multe răspunsuri, dar nici unul care menționează Obiect.crea de ECMAScript 5, care, desigur, nu-ți dau o copie exactă, dar stabilește sursa ca prototip al noului obiect.

Astfel, acest lucru nu este un răspuns exact la întrebare, dar este de o singură linie de soluție și, astfel, elegant. Și aceasta funcționează cel mai bine pentru 2 cazuri:

  1. În cazul în care o astfel de moștenire este util (duh!)
  2. În cazul în care obiectul sursă a câștigat't fi modificate, făcând astfel o legătură între cele 2 obiecte o non-problema.

Exemplu:

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

De ce nu am lua în considerare această soluție să fie superior? L's nativi, astfel, nu looping, nu recursivitate. Cu toate acestea, browsere mai vechi vor avea nevoie de un polyfill.

Comentarii (13)

Un mod elegant de a clona un obiect Javascript într-o singură linie de cod

Un `Obiect.atribuie metodă este parte a ECMAScript 2015 (ES6) standard și face exact ceea ce ai nevoie.

var clone = Object.assign({}, obj);

Obiect.assign() metodă este folosită pentru a copia valorile din toate nenumărate proprietăți proprii din unul sau mai multe obiecte de sursa de la un obiect țintă.

Citeste more...

La polyfill pentru susținerea browsere mai vechi:

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;
    }
  });
}
Comentarii (5)

Există mai multe probleme cu cele mai multe soluții pe internet. Așa că am decis să fac un follow-up, care include, de ce a acceptat răspunsul ar trebui't fi acceptat.

pornind de situație

Vreau sa profund-copie un Javascript "Obiect" cu toți copiii ei și copiii lor și așa mai departe. Dar de când am'm nu fel de normal producător meu "Obiect" are normal properties, structuri circulare "și chiar" obiecte imbricate.

Deci sa's a crea o structură circulară "și un" obiect imbricate prima.

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

Las's aduce totul împreună într-un "Obiect" nume "a".

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

Apoi, vrem să copie " a "într-o variabilă numită" b " și să-l transforme.

var b = a;

b.x = 'b';
b.nested.y = 'b';

Știi ce s-a întâmplat aici, pentru că dacă nu te-ar't chiar terenul pe această mare întrebare.

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

Acum las's a găsi o soluție.

JSON

Prima încercare am încercat a fost folosind JSON.

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

Don't pierde prea mult timp pe el,'ll TypeError: Convertirea structura circulară a JSON.

Recursiv copie (acceptat "răspuns")

Las's au o privire la răspunsul acceptat.

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.");
}

Arata bine, heh? L's o recursive copie a obiectului și se ocupă de alte tipuri, cum ar fi "Data", dar asta nu't o cerință.

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

Recursivitate și structuri circularenu&#39;t lucra bine împreună...RangeError: Maxim call stack size depășit`

soluție nativ

După cearta cu colegul meu, seful meu ne-a întrebat ce s-a întâmplat, și-a găsit o simplă soluție ** după unele googling. L's a numit Obiect.crea.

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

Această soluție a fost adăugat la Javascript ceva timp în urmă și chiar se ocupă de structură circulară.

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

și vezi, nu't de lucru cu imbricate în interiorul structurii.

polyfill pentru soluție nativ

Nu's un polyfill pentru Obiect.crea` în browser-ul vechi, doar ca IE 8. L's ceva de genul recomandate de Mozilla, și, desigur, l's nu este perfect și rezultatele în aceeași problemă ca soluție nativ.

function F() {};
function clonePF(o) {
    F.prototype = o;
    return new F();
}

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

Am'am pus " F " în afara domeniului de aplicare astfel încât să putem avea o privire la ceea ce instanceof ne spune.

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

Aceeasi problema ca soluție nativ, dar un pic mai rău de ieșire.

cel mai bine (dar nu perfect) soluție

Atunci când săpat în jurul valorii de, am găsit o întrebare similară (https://stackoverflow.com/questions/10728412/in-javascript-when-performing-a-deep-copy-how-do-i-avoid-a-cycle-due-to-a-pro) pentru asta, dar cu o modalitate mai bună soluție.


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
Comentarii (7)

Daca're bine cu o copie superficială, la underscore.js biblioteca are un clone metoda.

y = _.clone(x);

sau puteți să-l extindă ca

copiedObject = _.extend({},originalObject);
Comentarii (3)

OK imaginați-vă că au acest obiect de mai jos și doriți să clona:

let obj = {a:1, b:2, c:3}; //ES6

sau

var obj = {a:1, b:2, c:3}; //ES5

răspunsul este în principal depeneds pe care ECMAscript de tine, folosind, în ES6+, puteți folosi pur și simplu Obiectul.atribui sa faci clona:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

sau folosind răspândit operator de genul asta:

let cloned = {...obj}; //new {a:1, b:2, c:3};

Dar dacă folosești ES5, puteți folosi câteva metode, dar JSON.stringify, doar asigurați-vă că nu utilizați pentru o mare parte din date pentru a copia, dar ar putea fi o linie modalitate la îndemână în multe cazuri, ceva de genul asta:

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
Comentarii (5)

Unul deosebit de elegant soluție este de a utiliza JSON de codificare pentru a face profundă exemplare de obiecte care nu trebuie membru metode. Metodologia este de a JSON codifica obiect țintă, apoi de decodare, veți obține o copie sunteți în căutarea pentru. Tu poate decoda ori de câte ori doriți să faceți cât mai multe copii ca ai nevoie.

Desigur, funcțiile nu aparțin în JSON, astfel încât aceasta funcționează numai pentru obiecte fără membru metode.

Această metodologie a fost perfect pentru caz de utilizare, de când am'm depozitarea JSON blobs într-un magazin cheie-valoare, și atunci când acestea sunt expuse și obiecte în JavaScript API, fiecare obiect conține de fapt o copie a stării inițiale a obiectului astfel încât să putem calcula delta după ce apelantul a suferit mutații expuse obiect.

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value
Comentarii (4)

Puteți utiliza pur și simplu un răspândirea de proprietate pentru a copia un obiect fără referințe. Dar fii atent (vezi comentarii), ca 'copia' este doar pe cel mai mic obiect/matrice nivel. Imbricate proprietăți sunt încă referințe!


Complet clona:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

Clona cu trimiteri la al doilea nivel:

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 de fapt nu are suport adânc clone nativ. Utilizați o funcție de utilitate. De exemplu Ramda:

Comentarii (3)

Pentru cei care folosesc AngularJS, există, de asemenea, metoda directă pentru clonare sau extinderea obiectelor din această bibliotecă.

var destination = angular.copy(source);

sau

angular.copy(source, destination);

Mai mult, în unghiulare.copia documentația...

Comentarii (1)

A. Levy's răspunsul este aproape completă, aici e mica mea contribuție: există o modalitate de cum să se ocupe recursiv bibliografie, vezi linia asta

dacă(acest lucru[v]==acest lucru) copia[attr] = copie;

Dacă obiectul este XML DOM element, trebuie să utilizați cloneNode în loc

dacă(acest lucru.cloneNode) se întoarcă acest lucru.cloneNode(true);

Inspirat de A. Levy's studiu exhaustiv și Calvin's prototipuri abordare, am oferit această soluție:

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

A se vedea, de asemenea, Andy Burke's notă în răspunsuri.

Comentarii (2)

De la acest articol: Cum să copiați tablouri și obiecte în Javascript de 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;
};
Comentarii (6)

Aici este o funcție puteți folosi.

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;
}
Comentarii (4)

În ES-6 puteți folosi pur și simplu Obiect.atribui(...). Ex:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

Un bun punct de referință este aici: https://googlechrome.github.io/samples/object-assign-es6/

Comentarii (6)

În ECMAScript 2018

let objClone = { ...obj };

Fi conștienți de faptul că obiecte imbricate sunt încă copiat ca o referință.

Comentarii (4)

Nou răspuns la o întrebare veche! Dacă aveți plăcerea de a avea folosind ECMAScript 2016 (ES6) cu a Răspândit Sintaxa, l's ușor.

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

Acest lucru oferă o metoda curat pentru o copie superficială a unui obiect. A face o copie profundă, în sensul makign o copie nouă de fiecare valoare în fiecare recursiv obiecte imbricate, necesită pe de grele soluții de mai sus.

JavaScript continuă evoluție.

Comentarii (4)

Poti clona un obiect și de a elimina orice referire la precedenta, folosind o singură linie de cod. Pur și simplu:

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

Pentru browsere / motoare care nu acceptă în prezent Obiect.creați puteți utiliza această 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();
    };
}
Comentarii (7)