javascript - without - js shallow object copy




Was ist der effizienteste Weg, ein Objekt in JavaScript tief zu klonen? (20)

Was ist der effizienteste Weg, um ein JavaScript-Objekt zu klonen? Ich habe gesehen obj = eval(uneval(o)); verwendet wird, aber das ist nicht standardisiert und wird nur von Firefox unterstützt .

Ich habe Dinge wie obj = JSON.parse(JSON.stringify(o)); aber frag die Effizienz.

Ich habe auch rekursive Kopierfunktionen mit verschiedenen Fehlern gesehen.
Ich bin überrascht, dass es keine kanonische Lösung gibt.


Hinweis: Dies ist eine Antwort auf eine andere Antwort, keine richtige Antwort auf diese Frage. Wenn Sie schnelles Objektklonen wünschen, folgen Sie bitte den Empfehlungen von Corban bei der Beantwortung dieser Frage.

Ich möchte beachten, dass die .clone() -Methode in jQuery nur DOM-Elemente .clone() . Um JavaScript-Objekte zu klonen, würden Sie Folgendes tun:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Weitere Informationen finden Sie in der jQuery-Dokumentation .

Ich möchte auch anmerken, dass die tiefe Kopie tatsächlich viel intelligenter ist als das, was oben gezeigt wird - es kann viele Fallen vermeiden (z. B. beim Versuch, ein DOM-Element tief zu erweitern). Es wird häufig im jQuery-Core und in Plugins mit großer Wirkung verwendet.


Die effiziente Möglichkeit, ein Objekt in einer Codezeile zu klonen (nicht tief zu klonen)

Eine Object.assign Methode ist Teil des Standards ECMAScript 2015 (ES6) und erfüllt genau das, was Sie benötigen.

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

Die Object.assign () -Methode wird verwendet, um die Werte aller aufzählbaren eigenen Eigenschaften von einem oder mehreren Quellobjekten in ein Zielobjekt zu kopieren.

Object.assign

Die Polyfill zur Unterstützung älterer Browser:

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

AngularJS

Wenn Sie Winkel verwenden, können Sie dies auch tun

var newObject = angular.copy(oldObject);

Überprüfen Sie diesen Benchmark: http://jsben.ch/#/bWfk9

In meinen vorherigen Tests, bei denen Geschwindigkeit ein Hauptanliegen war, fand ich heraus

JSON.parse(JSON.stringify(obj))

um die schnellste Möglichkeit zu sein, ein Objekt tief zu klonen ( jQuery.extend mit 10-20% auf true gesetzt).

jQuery.extend ist ziemlich schnell, wenn das tiefe Flag auf false (shallow clone) gesetzt ist. Dies ist eine gute Option, da sie einige zusätzliche Logik für die Typüberprüfung enthält und keine undefinierten Eigenschaften usw. kopiert. Dies wird Sie jedoch etwas verlangsamen.

Wenn Sie die Struktur der Objekte kennen, die Sie zu klonen versuchen oder tiefe verschachtelte Arrays vermeiden können, können Sie eine einfache for (var i in obj) -Schleife schreiben, um Ihr Objekt zu klonen, während hasOwnProperty geprüft wird, und es wird viel schneller als jQuery sein.

Wenn Sie versuchen, eine bekannte Objektstruktur in einer Hot-Loop zu klonen, können Sie VIEL VIEL MEHR LEISTUNG erhalten, indem Sie einfach die Klonprozedur einbetten und das Objekt manuell erstellen.

JavaScript-Ablaufverfolgungs-Engines saugen bei der Optimierung for..in Schleifen und der Überprüfung von hasOwnProperty auch Sie langsamer. Manueller Klon, wenn Geschwindigkeit ein absolutes Muss ist.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

Verwenden Sie die JSON.parse(JSON.stringify(obj)) -Methode für Date Objekte. JSON.stringify(new Date()) gibt eine Zeichenfolgendarstellung des Datums im ISO-Format zurück, die JSON.parse() nicht zurück konvertiert zu einem Date Objekt. Weitere Informationen finden Sie in dieser Antwort .

Beachten Sie außerdem, dass zumindest in Chrome 65 das native Klonen nicht der richtige Weg ist. Laut dieser JSPerf ist das native Klonen mit dem Erstellen einer neuen Funktion fast 800x langsamer als mit JSON.stringify, was auf der ganzen Welt unglaublich schnell ist.


Code:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Prüfung:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

Das verwende ich:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

Ich weiß, dass dies ein alter Beitrag ist, aber ich dachte, das könnte der nächsten Person, die dahinter stolpert, helfen.

Solange Sie keinem Objekt ein Objekt zuweisen, behält es keinen Verweis im Speicher. Um ein Objekt zu erstellen, das Sie für andere Objekte freigeben möchten, müssen Sie eine Fabrik wie folgt erstellen:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

Wenn Sie es verwenden, verfügt die Bibliothek Underscore.js über eine clone .

var newObject = _.clone(oldObject);

Cloning eines Objekts war in JS immer ein Problem, aber vor ES6 ging es darum, verschiedene Möglichkeiten zum Kopieren eines Objekts in JavaScript unten aufzuführen. Stellen Sie sich vor, Sie haben das Objekt unten und möchten eine tiefe Kopie davon haben:

var obj = {a:1, b:2, c:3, d:4};

Es gibt einige Möglichkeiten, dieses Objekt zu kopieren, ohne den Ursprung zu ändern:

1) ES5 +, Verwenden einer einfachen Funktion zum Kopieren für Sie:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    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 this object.");
}

2) ES5 + mit JSON.parse und JSON.stringify.

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs & Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

Hoffe diese Hilfe ...


Tiefe Kopie nach Leistung: Bestnoten bis Schlechteste

  • Neuzuordnung "=" (nur String-Arrays, Zahlen-Arrays)
  • Slice (nur String-Arrays, nur Zahlen-Arrays)
  • Verkettung (nur String-Arrays, Zahlen-Arrays)
  • Benutzerdefinierte Funktion: für die Schleife oder rekursive Kopie
  • jQuery's $ .extend
  • JSON.parse (nur String-Arrays, Zahlen-Arrays, Objekt-Arrays)
  • _.Clone von Underscore.js (nur String-Arrays, Zahlen-Arrays)
  • Lo-Dash's _.cloneDeep

Kopieren Sie ein Array von Strings oder Zahlen (eine Ebene - keine Referenzzeiger) tief:

Wenn ein Array Zahlen und Strings enthält - Funktionen wie .slice (), .concat (), .splice (), der Zuweisungsoperator "=" und die Klonfunktion von Underscore.js; erstellt eine tiefe Kopie der Elemente des Arrays.

Wo die Neuzuweisung die schnellste Leistung aufweist:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

Und .slice () hat eine bessere Leistung als .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Kopieren Sie ein Array von Objekten tief (zwei oder mehr Ebenen - Referenzzeiger):

var arr1 = [{object:'a'}, {object:'b'}];

Schreiben Sie eine benutzerdefinierte Funktion (hat eine schnellere Leistung als $ .extend () oder JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Verwenden Sie Dienstprogramme von Drittanbietern:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

Wo jQuery's $ .extend eine bessere Leistung bringt:


Crockford schlägt vor (und ich bevorzuge), diese Funktion zu verwenden:

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

var newObject = object(oldObject);

Es ist knapp, funktioniert wie erwartet und Sie brauchen keine Bibliothek.

BEARBEITEN:

Dies ist ein Polyfill für Object.create, also können Sie diesen auch verwenden.

var newObject = Object.create(oldObject);

ANMERKUNG: Wenn Sie einige davon verwenden, kann es zu Problemen bei der Verwendung von Iterationen kommen hasOwnProperty. Denn createneues leeres Objekt erstellen , die erben oldObject. Es ist jedoch immer noch nützlich und praktisch, um Objekte zu klonen.

Zum Beispiel wenn oldObject.a = 5;

newObject.a; // is 5

aber:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

Hier ist eine Version von ConroyPs Antwort, die auch dann funktioniert, wenn der Konstruktor erforderliche Parameter hat:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Diese Funktion ist auch in meiner simpleoo Bibliothek verfügbar .

Bearbeiten:

Hier ist eine robustere Version (dank Justin McCandless werden jetzt auch zyklische Referenzen unterstützt):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

Dies ist im Allgemeinen nicht die effizienteste Lösung, aber es macht, was ich brauche. Einfache Testfälle unten ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Zyklischer Array-Test ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Funktionstest...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

Es scheint noch keinen idealen Deep-Clone-Operator für Array-ähnliche Objekte zu geben. Wie der folgende Code veranschaulicht, wandelt der jQuery-Cloner von John Resig Arrays mit nicht-numerischen Eigenschaften in Objekte um, die keine Arrays sind, und der JSON-Cloner von RegDwight löscht die nicht-numerischen Eigenschaften. Die folgenden Tests veranschaulichen diese Punkte in mehreren Browsern:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

Flacher Exemplar-Einzeiler ( ECMAScript 5. Auflage ):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

Ein flacher Exemplar ( ECMAScript 6. Ausgabe , 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

Hier ist eine umfassende clone () -Methode, die jedes JavaScript-Objekt klonen kann. Es behandelt fast alle Fälle:

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

Im Folgenden werden zwei Instanzen desselben Objekts erstellt. Ich habe es gefunden und benutze es derzeit. Es ist einfach und leicht zu bedienen.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

Lodash hat eine nette lodash.com/docs#cloneDeep -Methode:

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }




clone