Vraag Hoe kan ik een JavaScript-object correct klonen?


Ik heb een object, x. Ik zou het als object willen kopiëren y, zodanig dat wijzigingen in y niet wijzigen x. Ik besefte dat het kopiëren van objecten die zijn afgeleid van ingebouwde JavaScript-objecten extra, ongewenste eigenschappen tot gevolg hebben. Dit is geen probleem, omdat ik een van mijn eigen, letterlijk geconstrueerde objecten kopieer.

Hoe kan ik een JavaScript-object correct klonen?


2395


oorsprong


antwoorden:


Bijgewerkt antwoord

Gebruik gewoon Object.assign () zoals voorgesteld hier

Maar let op: dit maakt alleen een ondiep exemplaar. Geneste objecten worden nog steeds als referentie gekopieerd.


Verouderde antwoord

Om dit te doen voor elk object in JavaScript zal niet eenvoudig of ongecompliceerd zijn. U zult tegen het probleem aan lopen dat abusievelijk attributen worden opgehaald van het prototype van het object dat in het prototype zou moeten worden achtergelaten en niet naar de nieuwe instantie moet worden gekopieerd. Als u bijvoorbeeld een toevoegt clone methode om Object.prototype, zoals sommige antwoorden weergeven, moet u dat kenmerk expliciet overslaan. Maar wat als er andere aanvullende methoden aan worden toegevoegd Object.prototypeof andere tussenliggende prototypen waarvan u niet op de hoogte bent? In dat geval kopieert u attributen die u niet zou moeten, dus u moet onvoorziene, niet-lokale attributen detecteren met de hasOwnProperty methode.

Naast niet-opsombare attributen, zul je een moeilijker probleem tegenkomen wanneer je objecten probeert te kopiëren die verborgen eigenschappen hebben. Bijvoorbeeld, prototype is een verborgen eigenschap van een functie. Ook wordt naar het prototype van een object verwezen met het kenmerk __proto__, dat ook is verborgen en niet wordt gekopieerd door een for / in-lus die verwijst naar de kenmerken van het bronobject. I denk __proto__ kan specifiek zijn voor de JavaScript-interpreter van Firefox en het kan iets anders zijn in andere browsers, maar u krijgt de afbeelding. Niet alles is opsombaar. U kunt een verborgen attribuut kopiëren als u de naam kent, maar ik weet niet hoe ik het automatisch kan ontdekken.

Nog een ander probleem in de zoektocht naar een elegante oplossing is het probleem van het correct opzetten van de overerving van prototypen. Als het prototype van uw bronobject is Object, dan eenvoudigweg een nieuw algemeen object maken met {} zal werken, maar als het prototype van de bron een afstammeling is van Object, dan mis je de extra leden van dat prototype die je hebt overgeslagen met de hasOwnProperty filter, of die in het prototype waren, maar in de eerste plaats niet opsomden. Een mogelijke oplossing is om de bronobjecten te bellen constructor eigenschap om het initiële kopie-object te krijgen en vervolgens de attributen te kopiëren, maar dan krijgt u nog steeds geen niet-opsombare attributen. Bijvoorbeeld a Date object slaat zijn gegevens op als een verborgen lid:

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

De datumreeks voor d1 zal 5 seconden achter dat zijn van d2. Een manier om er een te maken Date hetzelfde als een andere is door de setTime methode, maar dat is specifiek voor de Date klasse. Ik denk niet dat er een kogelvrije algemene oplossing voor dit probleem is, maar ik zou het mis hebben als ik het mis had!

Toen ik algemeen diepkopiëren moest implementeren, eindigde ik met het compromitteren door aan te nemen dat ik alleen een gewone kopie hoefde te kopiëren Object, Array, Date, String, Numberof Boolean. De laatste 3 soorten zijn onveranderbaar, dus ik kon een ondiepe kopie maken en hoef me niet zorgen te maken dat het verandert. Ik ging er verder van uit dat alle elementen vervat in Object of Array zou ook een van de 6 eenvoudige typen in die lijst zijn. Dit kan worden bereikt met code zoals de volgende:

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

De bovenstaande functie werkt adequaat voor de 6 eenvoudige typen die ik heb genoemd, zolang de gegevens in de objecten en arrays een boomstructuur vormen. Dat wil zeggen, er is niet meer dan één verwijzing naar dezelfde gegevens in het object. Bijvoorbeeld:

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

Het zal niet in staat zijn om een ​​JavaScript-object aan te pakken, maar het kan voldoende zijn voor veel doeleinden, zolang je er niet van uitgaat dat het gewoon werkt voor alles wat je erin gooit.


1308



Met jQuery, kunt u Oppervlakkige kopie met uitbreiden:

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

latere wijzigingen aan het gekopieerde object hebben geen invloed op het originalObject en omgekeerd.

Of om een ​​te maken diepe kopie:

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

712



Als u geen functies binnen uw object gebruikt, kan een zeer eenvoudige voering het volgende zijn:

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

Dit werkt voor alle soorten objecten die objecten, arrays, strings, booleans en getallen bevatten.

Zie ook dit artikel over het gestructureerde kloonalgoritme van browsers die wordt gebruikt bij het plaatsen van berichten van en naar een medewerker. Het bevat ook een functie voor diep klonen.


687



In ECMAScript 6 is dat zo Object.assign methode, die waarden van alle opsombare eigen eigenschappen van het ene object naar het andere kopieert. Bijvoorbeeld:

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

Houd er echter rekening mee dat geneste objecten nog steeds worden gekopieerd als referentie.


508



Er zijn veel antwoorden, maar geen die vermeldt Object.create van ECMAScript 5, die je weliswaar niet een exacte kopie geeft, maar de bron als het prototype van het nieuwe object instelt.

Dit is dus geen exact antwoord op de vraag, maar het is een one-line oplossing en dus elegant. En het werkt het beste voor 2 gevallen:

  1. Waar zo'n overerving nuttig is (duh!)
  2. Waar het bronobject niet zal worden gewijzigd, waardoor de relatie tussen de 2 objecten een probleem vormt.

Voorbeeld:

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

Waarom vind ik deze oplossing superieur? Het is native, dus geen looping, geen recursie. Oudere browsers hebben echter een polyfill nodig.


113



Een elegante manier om een ​​JavaScript-object in een regel code te klonen

Een Object.assign methode maakt deel uit van de ECMAScript 2015 (ES6) -standaard en doet precies wat u nodig hebt.

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

De methode Object.assign () wordt gebruikt om de waarden van alle opsombare eigen eigenschappen van een of meer bronobjecten naar een doelobject te kopiëren.

Lees verder...

De polyfill om oudere browsers te ondersteunen:

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

105



Per MDN:

  • Gebruik als u een ondiepe kopie wilt Object.assign({}, a)
  • Gebruik voor "diepe" kopie JSON.parse(JSON.stringify(a))

U hebt geen externe bibliotheken nodig, maar u moet dit controleren browsercompatibiliteit eerst.


103



Als u het goed vindt met een ondiepe kopie, bevat de bibliotheek underscore.js een kloon methode.

y = _.clone(x);

of je kunt het uitbreiden zoals

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

70



Er zijn verschillende problemen met de meeste oplossingen op internet. Dus besloot ik een follow-up uit te voeren, waarin staat waarom het geaccepteerde antwoord niet moet worden geaccepteerd.

beginsituatie

ik wil deep-copy een Javascript Object met al zijn kinderen en hun kinderen enzovoort. Maar aangezien ik geen normale ontwikkelaar ben, mijn Object heeft normaal  properties, circular structures en zelfs nested objects.

Dus laten we een maken circular structure en een nested object eerste.

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

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

Laten we alles samenbrengen in een Object genaamd a.

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

Vervolgens willen we kopiëren a in een variabele met de naam b en muteer het.

var b = a;

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

Je weet wat hier gebeurde, want anders zou je niet eens op deze geweldige vraag terechtkomen.

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

Laten we nu een oplossing zoeken.

JSON

De eerste poging die ik probeerde was te gebruiken JSON.

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

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

Verspil er niet te veel tijd aan, je zult het krijgen TypeError: Converting circular structure to JSON.

Recursief exemplaar (het geaccepteerde "antwoord")

Laten we naar het geaccepteerde antwoord kijken.

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

Ziet er goed uit, hè? Het is een recursieve kopie van het object en verwerkt ook andere typen, zoals Date, maar dat was geen vereiste.

var b = cloneSO(a);

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

Recursie en circular structures werkt niet goed samen ... RangeError: Maximum call stack size exceeded

eigen oplossing

Na ruzie met mijn collega, vroeg mijn baas ons wat er gebeurde, en hij vond een simpele oplossing na wat googelen. Het heet Object.create.

var b = Object.create(a);

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

Deze oplossing is enige tijd geleden aan Javascript toegevoegd en kan zelfs worden verwerkt 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"
    }
}

... en zie je, het werkte niet met de geneste structuur erin.

polyfill voor de native oplossing

Er is een polyfill voor Object.create in de oudere browser net als de IE 8. Het is iets dat wordt aanbevolen door Mozilla, en natuurlijk is het niet perfect en resulteert in hetzelfde probleem als de eigen oplossing.

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

var b = clonePF(a);

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

Ik heb gezet F buiten de scope zodat we kunnen kijken wat instanceof vertel ons.

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

Hetzelfde probleem als de eigen oplossing, maar een iets slechtere uitvoer.

de betere (maar niet perfecte) oplossing

Bij het graven vond ik een vergelijkbare vraag (Hoe voorkom ik bij het uitvoeren van een deep-copy in Javascript een cyclus, omdat een eigenschap "this" is?), maar met een veel betere oplossing.

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

En laten we eens kijken naar de uitvoer ...

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

De vereisten zijn aangepast, maar er zijn nog steeds enkele kleinere problemen, waaronder het wijzigen van de instance van nested en circ naar Object.

De structuur van bomen die een blad delen, wordt niet gekopieerd, ze worden twee onafhankelijke bladeren:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

conclusie

De laatste oplossing met behulp van recursie en een cache, is misschien niet de beste, maar het is een echt deep-copy van het object. Het behandelt eenvoudig properties, circular structures en nested object, maar het zal de instantie ervan verknoeien tijdens het klonen.

http://jsfiddle.net/einfallstoll/N4mr2/


63