3750 Stimmen

Wie kann ich ein JavaScript-Objekt korrekt klonen?

Ich habe ein Objekt x . Ich würde es gerne als Objekt kopieren y , so dass Änderungen an y nicht verändern x . Ich habe festgestellt, dass das Kopieren von Objekten, die von integrierten JavaScript-Objekten abgeleitet sind, zu zusätzlichen, unerwünschten Eigenschaften führt. Das ist kein Problem, da ich eines meiner eigenen, wörtlich konstruierten Objekte kopiere.

Wie kann ich ein JavaScript-Objekt korrekt klonen?

35 Stimmen

0 Stimmen

Unterstützen Sie auf jeden Fall @Niyaz! Shortlink: tinyurl.com/JSCopyObject

290 Stimmen

Für JSON verwende ich mObj=JSON.parse(JSON.stringify(jsonObject));

29voto

Lukas Jelinek Punkte 2167

Für diejenigen, die AngularJS verwenden, gibt es auch eine direkte Methode zum Klonen oder Erweitern der Objekte in dieser Bibliothek.

var destination = angular.copy(source);

o

angular.copy(source, destination);

Mehr in angular.copy Dokumentation ...

3 Stimmen

Dies ist eine Deep Copy FYI.

27voto

picardo Punkte 23796
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;
}

11 Stimmen

Diese Antwort ist ziemlich genau, aber nicht ganz richtig. Wenn Sie versuchen, ein Date-Objekt zu klonen, erhalten Sie nicht dasselbe Datum, da der Aufruf der Date-Konstruktorfunktion das neue Datum mit dem aktuellen Datum/Uhrzeit initialisiert. Dieser Wert ist nicht aufzählbar und wird von der for/in-Schleife nicht kopiert.

0 Stimmen

Nicht perfekt, aber gut für diese einfachen Fälle. Z.B. einfaches Klonen eines Arguments, das ein einfaches Objekt, Array oder String sein kann.

0 Stimmen

Hochgestuft für den korrekten Aufruf des Konstruktors mit new . Die akzeptierte Antwort tut dies nicht.

26voto

Jan Turoň Punkte 28722

Die Antwort von A.Levy ist fast vollständig, hier ist mein kleiner Beitrag: es gibt eine Möglichkeit, rekursive Verweise zu behandeln siehe diese Zeile

if(this[attr]==this) copy[attr] = copy;

Wenn das Objekt ein XML-DOM-Element ist, müssen wir cloneNode stattdessen

if(this.cloneNode) return this.cloneNode(true);

Inspiriert von der umfassenden Studie von A. Levy und dem Prototyping-Ansatz von Calvin, biete ich diese Lösung an:

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

Siehe auch die Anmerkung von Andy Burke in den Antworten.

3 Stimmen

Date.prototype.clone = function() {return new Date(+this)};

25voto

Kamil Kiełczewski Punkte 69048

Leistung

Heute 2020.04.30 führe ich Tests der gewählten Lösungen auf Chrome v81.0, Safari v13.1 und Firefox v75.0 auf MacOs High Sierra v10.13.6 durch.

Ich konzentriere mich auf die Geschwindigkeit des Kopierens von DATA (Objekt mit einfachen Feldern, nicht Methoden usw.). Die Lösungen A-I können nur flache Kopien erstellen, die Lösungen J-U können tiefe Kopien erstellen.

Ergebnisse für flache Kopien

  • Lösung {...obj} (A) ist am schnellsten auf Chrome und Firefox und mittelschnell auf Safari
  • Lösung basierend auf Object.assign (B) ist auf allen Browsern schnell
  • jQuery (E) und Lodash (F,G,H) sind mittelschnell/recht schnell
  • Lösung JSON.parse/stringify (K) ist recht langsam
  • Die Lösungen D und U sind auf allen Browsern langsam

enter image description here

Ergebnisse für Deep Copy

  • Lösung Q ist auf allen Browsern am schnellsten
  • jQuery (L) und lodash (J) sind mittelschnell
  • Lösung JSON.parse/stringify (K) ist recht langsam
  • Lösung U ist bei allen Browsern am langsamsten
  • lodash (J) und Lösung U stürzen in Chrome bei 1000 Level tiefen Objekten ab

enter image description here

Einzelheiten

Für gewählte Lösungen: A B C(my) D E F G H I J K L M N O P Q R S T U , Ich führe 4 Tests durch

  • shallow-small: Objekt mit 10 nicht verschachtelten Feldern - Sie können es ausführen HIER
  • shallow-big: Objekt mit 1000 nicht verschachtelten Feldern - Sie können es ausführen HIER
  • deep-small: Objekt mit 10 Ebenen-verschachtelten Feldern - Sie können es ausführen HIER
  • deep-big: Objekt mit 1000 Ebenen-verschachtelten Feldern - Sie können es ausführen HIER

Die in den Tests verwendeten Objekte sind im folgenden Ausschnitt dargestellt

let obj_ShallowSmall = {
  field0: false,
  field1: true,
  field2: 1,
  field3: 0,
  field4: null,
  field5: [],
  field6: {},
  field7: "text7",
  field8: "text8",
}

let obj_DeepSmall = {
  level0: {
   level1: {
    level2: {
     level3: {
      level4: {
       level5: {
        level6: {
         level7: {
          level8: {
           level9: [[[[[[[[[['abc']]]]]]]]]],
  }}}}}}}}},
};

let obj_ShallowBig = Array(1000).fill(0).reduce((a,c,i) => (a['field'+i]=getField(i),a) ,{});

let obj_DeepBig = genDeepObject(1000);

// ------------------
// Show objects
// ------------------

console.log('obj_ShallowSmall:',JSON.stringify(obj_ShallowSmall));
console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall));
console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig));
console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig));

// ------------------
// HELPERS
// ------------------

function getField(k) {
  let i=k%10;
  if(i==0) return false;
  if(i==1) return true;
  if(i==2) return k;
  if(i==3) return 0;
  if(i==4) return null;
  if(i==5) return [];
  if(i==6) return {};  
  if(i>=7) return "text"+k;
}

function genDeepObject(N) {
  // generate: {level0:{level1:{...levelN: {end:[[[...N-times...['abc']...]]] }}}...}}}
  let obj={};
  let o=obj;
  let arr = [];
  let a=arr;

  for(let i=0; i<N; i++) {
    o['level'+i]={};
    o=o['level'+i];
    let aa=[];
    a.push(aa);
    a=aa;
  }

  a[0]='abc';
  o['end']=arr;
  return obj;
}

Das folgende Snippet zeigt die getesteten Lösungen und die Unterschiede zwischen ihnen

function A(obj) {
  return {...obj}
}

function B(obj) {
  return Object.assign({}, obj); 
}

function C(obj) {
  return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), {})
}

function D(obj) {
  let copyOfObject = {};
  Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj));
  return copyOfObject;
}

function E(obj) {
  return jQuery.extend({}, obj) // shallow
}

function F(obj) {
  return _.clone(obj);
}

function G(obj) {
  return _.clone(obj,true);
}

function H(obj) {
  return _.extend({},obj);
}

function I(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;
}

function J(obj) {
  return _.cloneDeep(obj,true);
}

function K(obj) {
    return JSON.parse(JSON.stringify(obj));
}

function L(obj) {
  return jQuery.extend(true, {}, obj) // deep
}

function M(obj) {
  if(obj == null || typeof(obj) != 'object')
    return obj;    
  var temp = new obj.constructor(); 
  for(var key in obj)
    temp[key] = M(obj[key]);    
  return temp;
}

function N(obj) {
  let EClone = function(obj) {
    var newObj = (obj instanceof Array) ? [] : {};
    for (var i in obj) {
      if (i == 'EClone') continue;
      if (obj[i] && typeof obj[i] == "object") {
        newObj[i] = EClone(obj[i]);
      } else newObj[i] = obj[i]
    } return newObj;
  };

    return EClone(obj);
};

function O(obj) {
    if (obj == null || typeof obj != "object") return obj;
    if (obj.constructor != Object && obj.constructor != Array) return obj;
    if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function ||
        obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean)
        return new obj.constructor(obj);

    let to = new obj.constructor();

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

    return to;
}

function P(obj) {
  function clone(target, source){

      for(let key in source){

          // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
          let descriptor = Object.getOwnPropertyDescriptor(source, key);
          if(descriptor.value instanceof String){
              target[key] = new String(descriptor.value);
          }
          else if(descriptor.value instanceof Array){
              target[key] = clone([], descriptor.value);
          }
          else if(descriptor.value instanceof Object){
              let prototype = Reflect.getPrototypeOf(descriptor.value);
              let cloneObject = clone({}, descriptor.value);
              Reflect.setPrototypeOf(cloneObject, prototype);
              target[key] = cloneObject;
          }
          else {
              Object.defineProperty(target, key, descriptor);
          }
      }
      let prototype = Reflect.getPrototypeOf(source);
      Reflect.setPrototypeOf(target, prototype);
      return target;
  }
  return clone({},obj);
}

function Q(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] = Q(obj[i]);
        }
        return copy;
    }

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

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

function R(obj) {
    const gdcc = "__getDeepCircularCopy__";
    if (obj !== Object(obj)) {
        return obj; // primitive value
    }

    var set = gdcc in obj,
        cache = obj[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    obj[gdcc] = function() { return result; }; // overwrite
    if (obj instanceof Array) {
        result = [];
        for (var i=0; i<obj.length; i++) {
            result[i] = R(obj[i]);
        }
    } else {
        result = {};
        for (var prop in obj)
            if (prop != gdcc)
                result[prop] = R(obj[prop]);
            else if (set)
                result[prop] = R(cache);
    }
    if (set) {
        obj[gdcc] = cache; // reset
    } else {
        delete obj[gdcc]; // unset again
    }
    return result;
}

function S(obj) {
    const cache = new WeakMap(); // Map of old - new references

    function copy(object) {
        if (typeof object !== 'object' ||
            object === null ||
            object instanceof HTMLElement
        )
            return object; // primitive value or HTMLElement

        if (object instanceof Date) 
            return new Date().setTime(object.getTime());

        if (object instanceof RegExp) 
            return new RegExp(object.source, object.flags);

        if (cache.has(object)) 
            return cache.get(object);

        const result = object instanceof Array ? [] : {};

        cache.set(object, result); // store reference to object before the recursive starts

        if (object instanceof Array) {
            for(const o of object) {
                 result.push(copy(o));
            }
            return result;
        }

        const keys = Object.keys(object); 

        for (const key of keys)
            result[key] = copy(object[key]);

        return result;
    }

    return copy(obj);
}

function T(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") 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; i < obj.length; ++i) {
                copy[i] = cloneRecursive(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       

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

    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

function U(obj) {
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(obj)

  function defineProp(object, key, descriptor = {}, copyFrom = {}) {
    const { configurable: _configurable, writable: _writable }
      = Object.getOwnPropertyDescriptor(object, key)
      || { configurable: true, writable: true }

    const test = _configurable // Can redefine property
      && (_writable === undefined || _writable) // Can assign to property

    if (!test || arguments.length <= 2) return test

    const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
      || { configurable: true, writable: true } // Custom…
      || {}; // …or left to native default settings

    ["get", "set", "value", "writable", "enumerable", "configurable"]
      .forEach(attr =>
        descriptor[attr] === undefined &&
        (descriptor[attr] = basisDesc[attr])
      )

    const { get, set, value, writable, enumerable, configurable }
      = descriptor

    return Object.defineProperty(object, key, {
      enumerable, configurable, ...get || set
        ? { get, set } // Accessor descriptor
        : { value, writable } // Data descriptor
    })
  }

  function clone(object) {
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) {
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        const fnStr = String(object)

        _object = new Function("return " +
          (/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr)
            ? "function " : ""
          ) + fnStr
        )()

        copyPropDescs(_object, object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) {
          //                              // Stem from:
          case "[object Function]":       // `class`
          case "[object Undefined]":      // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:                        // `Proxy`
            _object = object
        }
    }

    return _object
  }

  function cloneObject(object) {
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key, { value: clone(object[key]) }, object)
    )

    return _object
  }

  function copyPropDescs(target, source) {
    Object.defineProperties(target,
      Object.getOwnPropertyDescriptors(source)
    )
  }
}

// ------------------------
// Test properties
// ------------------------

console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt`)

log(A);
log(B);
log(C);
log(D);
log(E);
log(F);
log(G);
log(H);
log(I);
log(J);
log(K);
log(L);
log(M);
log(N);
log(O);
log(P);
log(Q);
log(R);
log(S);
log(T);
log(U);

console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt
----
LEGEND:
shallow - solution create shallow copy
deep - solution create deep copy
func - solution copy functions
circ - solution can copy object with circular references
undefined - solution copy fields with undefined value
date - solution can copy date
RegExp - solution can copy fields with regular expressions
bigInt - solution can copy BigInt
`)

// ------------------------
// Helper functions
// ------------------------

function deepCompare(obj1,obj2) {
  return JSON.stringify(obj1)===JSON.stringify(obj2);
}

function getCase() { // pure data case
  return { 
    undef: undefined,
    bool: true, num: 1, str: "txt1",    
    e1: null, e2: [], e3: {}, e4: 0, e5: false,
    arr: [ false, 2, "txt3", null, [], {},
      [ true,4,"txt5",null, [], {},  [true,6,"txt7",null,[],{} ], 
        {bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
      ],
        {bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
    ], 
    obj: { 
        bool: true, num: 12, str: "txt13",
        e1: null, e2: [], e3: {}, e4: 0, e5: false,
        arr: [true,14,"txt15",null,[],{} ],
        obj: { 
          bool: true, num: 16, str: "txt17",
          e1: null, e2: [], e3: {}, e4: 0, e5: false,
          arr: [true,18,"txt19",null,[],{} ],
          obj: {bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
      } 
    } 
  };
}

function check(org, copy, field, newValue) {
  copy[field] = newValue;
  return deepCompare(org,copy); 
}

function testFunc(f) {
    let o = { a:1, fun: (i,j)=> i+j };
  let c = f(o);

  let val = false
  try{
    val = c.fun(3,4)==7;
  } catch(e) { }
  return val;
} 

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

  var o = {
      x: 'a',
      circ: new Circ(),
      obj_circ: null,
  };

  o.obj_circ = o;

  let val = false;

  try{
    let c = f(o);  
    val = (o.obj_circ == o) && (o.circ == o.circ.me);
  } catch(e) { }
  return val;
} 

function testRegExp(f) {
  let o = {
    re: /a[0-9]+/,
  };

  let val = false;

  try{
    let c = f(o);  
    val = (String(c.re) == String(/a[0-9]+/));
  } catch(e) { }
  return val;
}

function testDate(f) {
  let o = {
    date: new Date(),
  };

  let val = false;

  try{
    let c = f(o);  
    val = (+new Date(c.date) == +new Date(o.date));
  } catch(e) { }
  return val;
}

function testBigInt(f) {
  let val = false;

  try{
    let o = {
      big: 123n,
    };

    let c = f(o);  

    val = o.big == c.big;
  } catch(e) { }

  return val;
}

function log(f) {
  let o = getCase();  // orginal object
  let oB = getCase(); // "backup" used for shallow valid test

  let c1 = f(o); // copy 1 for reference
  let c2 = f(o); // copy 2 for test shallow values
  let c3 = f(o); // copy 3 for test deep values

  let is_proper_copy = deepCompare(c1,o);  // shoud be true

  // shallow changes
  let testShallow = 
    [ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',{}] ]
    .reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true );

  // should be true (original object shoud not have changed shallow fields)
  let is_valid = deepCompare(o,oB); 

  // deep test (intruduce some change)
  if (c3.arr[6]) c3.arr[6][7].num = 777;

  let diff_shallow = !testShallow; // shoud be true (shallow field was copied)
  let diff_deep = !deepCompare(c1,c3);    // shoud be true (deep field was copied)
  let can_copy_functions = testFunc(f);
  let can_copy_circular = testCirc(f);
  let can_copy_regexp = testRegExp(f);
  let can_copy_date = testDate(f);
  let can_copy_bigInt = testBigInt(f);

  let has_undefined = 'undef' in c1; // field with undefined value is copied?  
  let is_ok = is_valid && is_proper_copy;
  let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string

  testFunc(f);

  if(is_ok) {
    console.log(`${f.name} ${b(diff_shallow)}   ${b(diff_deep)} ${b(can_copy_functions)} ${b(can_copy_circular)} ${b(has_undefined)}     ${b(can_copy_date)} ${b(can_copy_regexp)}  ${b(can_copy_bigInt)}`)
  } else {
    console.log(`${f.name}: INVALID ${is_valid} ${is_proper_copy}`,{c1})
  }

}

<script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>

This snippet only presents tested solutions and show differences between them (but it no make performence tests)

Nachfolgend finden Sie Beispielergebnisse für Chrome für shallow-big object

enter image description here

24voto

VaZaA Punkte 388

Lodash verwenden:

var y = _.clone(x, true);

6 Stimmen

OMG: Es wäre verrückt, das Klonen neu zu erfinden. Das ist die einzig vernünftige Antwort.

7 Stimmen

Ich bevorzuge _.cloneDeep(x) da es im Wesentlichen dasselbe ist wie oben, sich aber besser liest.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X