Hur klonar jag ett JavaScript-objekt korrekt?


Jag har ett objekt x. Jag skulle vilja kopiera det som objekt y, så att ändringar i y inte ändrar x. Jag insåg att kopiering av objekt som härstammar från inbyggda JavaScript-objekt kommer att resultera i extra, oönskade egenskaper. Detta är dock inget problem, eftersom jag kopierar ett av mina egna literal-konstruerade objekt.

Hur klonar jag ett JavaScript-objekt korrekt?

Svar #1

2022 års uppdatering

Det finns en ny JS-standard som kallas strukturerad kloning. Den fungerar i många webbläsare (se Can I Use).

const clone = structuredClone(object);

Gammalt svar

Att göra detta för vilket objekt som helst i JavaScript kommer inte att vara enkelt eller rakt på sak. Du kommer att stöta på problemet med att felaktigt plocka upp attribut från objektets prototyp som borde lämnas i prototypen och inte kopieras till den nya instansen. Om du till exempel lägger till en clone-metod till Object.prototype, som vissa svar visar, måste du uttryckligen hoppa över det attributet. Men vad händer om det finns andra ytterligare metoder tillagda till Object.prototype, eller andra mellanliggande prototyper, som du inte känner till? I så fall kommer du att kopiera attribut du inte borde, så du behöver upptäcka oförutsedda, icke-lokala attribut med hasOwnProperty-metoden.

Förutom icke-uppräkningsbara attribut kommer du att stöta på ett svårare problem när du försöker kopiera objekt som har dolda egenskaper. Till exempel är prototype en dold egenskap hos en funktion. Dessutom refereras ett objekts prototyp med attributet __proto__, som också är dolt och inte kommer att kopieras av en for/in-loop som itererar över källobjektets attribut. Jag tror att __proto__ kan vara specifikt för Firefox JavaScripts tol och det kan vara något annat i andra webbläsare, men du förstår poängen. Inte allt är uppräkningsbart. Du kan kopiera ett dolt attribut om du känner till dess namn, men jag känner inte till något sätt att upptäcka det automatiskt.

Ytterligare ett hinder i jakten på en elegant lösning är problemet med att ställa in prototyparvet korrekt. Om din källobjekts prototyp är Object, kommer det enkelt att fungera att skapa ett nytt generellt objekt med {}, men om källans prototyp är någon efterföljare till Object, kommer du att sakna de extra medlemmarna från den prototypen som du hoppade över med hasOwnProperty-filtret, eller som fanns i prototypen men inte var uppräkningsbara från början. En lösning kan vara att anropa källobjektets constructor-egenskap för att få initialkopieringsobjektet och sedan kopiera över egenskaperna, men då får du ändå inte icke-uppräkningsbara egenskaper. Till exempel lagrar ett Date-objekt sina data som en dold medlem:

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

Datumsträngen för d1 kommer att vara 5 sekunder efter den för d2. Ett sätt att göra ett Date samma som ett annat är genom att anropa setTime-metoden, men det är specifikt för Date-klassen. Jag tror inte att det finns en idiotsäker generell lösning på detta problem, även om jag skulle vara glad att ha fel!

När jag var tvungen att implementera generell djupkopiering slutade jag med att kompromissa genom att anta att jag endast skulle behöva kopiera ett vanligt Object, Array, Date, String, Number eller Boolean. De sista 3 typerna är oföränderliga, så jag kunde utföra en ytlig kopia utan att oroa mig för att det skulle förändras. Jag antog vidare att alla element som fanns i Object eller Array också skulle vara en av de 6 enkla typerna i den listan. Detta kan uppnås med kod som följer:

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

Ovanstående funktion kommer att fungera tillfredsställande för de 6 enkla typer jag nämnde, så länge som datan i objekten och arrayerna bildar en trädstruktur. Det vill säga, det finns inte mer än en referens till samma data i objektet. Till exempel:

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

Det kommer inte kunna hantera något JavaScript-objekt, men det kan vara tillräckligt för många ändamål så länge du inte antar att det bara fungerar för allt du kastar på det.

Svar #2

Om du inte använder Date, funktioner, undefined, regExp eller Infinity inom ditt objekt, är en mycket enkel enradare JSON.parse(JSON.stringify(object)):

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

Detta fungerar för alla typer av objekt som innehåller objekt, arrayer, strängar, booleaner och nummer.

Se även denna artikel om webbläsarnas strukturerade klonalgoritm som används när meddelanden skickas till och från en worker. Den innehåller också en funktion för djupkloning.

Svar #3

I ECMAScript 6 finns metoden Object.assign, som kopierar värden från alla uppräkningsbara egna egenskaper från ett objekt till ett annat. Till exempel:

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

Men var medveten om detta är en ytlig kopia - nästlade objekt kopieras fortfarande som referens.

Svar #4

Med jQuery kan du ytligt kopiera med extend:

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

efterföljande ändringar av copiedObject kommer inte att påverka originalObject, och vice versa.

Eller för att göra en djup kopia:

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

Svar #5

Enligt MDN:

  • Om du vill ha en ytlig kopia, använd Object.assign({}, a)
  • För "djup" kopia, använd JSON.parse(JSON.stringify(a))

Det finns inget behov av externa bibliotek men du behöver kontrollera webbläsarkompatibilitet först.

Svar #6

Ett elegant sätt att klona ett Javascript-objekt på en rad kod:

En Object.assign-metod är en del av ECMAScript 2015 (ES6)-standarden och gör exakt vad du behöver.

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

Metoden Object.assign() används för att kopiera värdena av alla uppräkneliga egna egenskaper från ett eller flera källobjekt till ett målobjekt.

Läs mer...

Polyfillen för att stödja äldre webbläsare:

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

Svar #7

Det finns många svar, men inget som nämner Object.create från ECMAScript 5, vilket visserligen inte ger dig en exakt kopia, men sätter källan som prototyp för det nya objektet.

Således är detta inte ett exakt svar på frågan, men det är en enradslösning och därmed elegant. Och det fungerar bäst för 2 fall:

  1. Där sådan arv är användbar (duh!)
  2. Där källobjektet inte kommer att ändras, vilket gör relationen mellan de två objekten obetydlig.

Exempel :

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

Varför anser jag att denna lösning är överlägsen? Den är inbyggd, alltså ingen loopning, ingen rekursion. Däremot kommer äldre webbläsare att behöva en polyfill.

Svar #8

Det finns flera problem med de flesta lösningar på internet. Så jag bestämde mig för att göra en uppföljning, som inkluderar varför det accepterade svaret inte borde accepteras.

startposition

Jag vill göra en djupkopiering av ett Javascript Object med alla dess barn och deras barn och så vidare. Men eftersom jag inte är någon normal utvecklare, har mitt Object vanliga properties, cirkulära strukturer och till och med nestade objekt.

Så låt oss först skapa en cirkulär struktur och ett nästlat objekt.

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

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

Låt oss samla allt i ett Object med namnet a.

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

Nästa steg är att kopiera a till en variabel som heter b och ändra den.

var b = a;

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

Du vet vad som hände här eftersom om inte det skulle du inte ens hamna på denna fantastiska fråga.

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

Nu låt oss hitta en lösning.

JSON

Det första försöket jag gjorde var att använda JSON.

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

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

Slösa inte för mycket tid på det, du kommer få TypeError: Converting circular structure to JSON.

Rekursiv kopiering (det accepterade "svar")

Låt oss titta på det accepterade svaret.

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

Ser bra ut, eller hur? Det är en rekursiv kopia av objektet och hanterar även andra typer, som Date, men det var inget krav.

var b = cloneSO(a);

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

Rekursion och cirkulära strukturer fungerar inte bra tillsammans... RangeError: Maximal storlek på anropsstacken överskriden

naturlig lösning

Efter att ha bråkat med min kollega frågade min chef oss vad som hade hänt, och han hittade en enkel lösning efter lite googlande. Den kallas Object.create.

var b = Object.create(a);

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

Denna lösning lades till i Javascript för ganska länge sedan och hanterar till och med cirkulär struktur.

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

... och du ser, det fungerade inte med den inbäddade strukturen inuti.

polyfill för den inbyggda lösningen

Det finns en polyfill för Object.create i äldre webbläsare precis som IE 8. Det är något som rekommenderas av Mozilla, och naturligtvis är det inte perfekt och resulterar i samma problem som den inbyggda lösningen.

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

var b = clonePF(a);

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

Jag har placerat F utanför scopet så att vi kan titta på vad instanceof berättar för oss.

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

Samma problem som den inhemska lösningen, men med lite sämre resultat.

den bättre (men inte perfekta) lösningen

När jag grävde runt, hittade jag en liknande fråga (I Javascript, när man utför en djup kopia, hur undviker man en cykel, på grund av en egenskap som är "this"?) till denna, men med en mycket bättre lösning.

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

Och låt oss ta en titt på resultatet...

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

Kravuppställningen matchas, men det finns fortfarande några mindre problem, inklusive att ändra instance av nested och circ till Object.

Strukturen hos träd som delar på ett blad kommer inte att kopieras, de kommer att bli två oberoende blad:

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

slutsats

Den sista lösningen som använder rekursion och en cache kanske inte är den bästa, men det är en verklig djupkopiering av objektet. Den hanterar enkla egenskaper, cirkulära strukturer och nästlade objekt, men den kommer att störa instansen av dem under kloningen.

jsfiddle

Svar #9

Om du är okej med en ytlig kopia, har biblioteket underscore.js en klonmetod.

y = _.clone(x);

eller så kan du förlänga det som

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

Svar #10

OK, föreställ dig att du har detta objekt nedan och du vill klona det:

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

eller

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

svaret beror huvudsakligen på vilken ECMAscript du använder, i ES6+, kan du enkelt använda Object.assign för att göra klonen:

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

eller använda spread-operatorn så här:

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

Men om du använder ES5, kan du använda några metoder, men JSON.stringify, se bara till att du inte använder det för en stor mängd data att kopiera, men det kan vara ett praktiskt sätt på en rad i många fall, något som detta:

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

Svar #11

Uppdatering 06 juli 2020

Det finns tre (3) sätt att klona objekt i JavaScript. Eftersom objekt i JavaScript är referensvärden kan du inte bara kopiera genom att använda =.

Sätten är:

const food = { food: 'apple', drink: 'milk' }


// 1. Using the "Spread"
// ------------------

{ ...food }


// 2. Using "Object.assign"
// ------------------

Object.assign({}, food)


// 3. "JSON"
// ------------------

JSON.parse(JSON.stringify(food))

// RESULT:
// { food: 'apple', drink: 'milk' }

Detta kan användas som en referenssammanfattning.

Svar #12

En särskilt ostenlig lösning är att använda JSON-kodning för att göra djupa kopior av objekt som inte har medlemsmetoder. Metodiken är att JSON-koda ditt målobjekt, och genom att avkoda det får du den kopia du letar efter. Du kan avkoda så många gånger du vill för att göra så många kopior som du behöver.

Självklart hör funktioner inte hemma i JSON, så detta fungerar bara för objekt utan medlemmetoder.

Denna metodik var perfekt för mitt användningsfall, eftersom jag lagrar JSON-blobs i en nyckel-värde-butik, och när de exponeras som objekt i ett JavaScript-API innehåller varje objekt faktiskt en kopia av objektets ursprungliga tillstånd så att vi kan beräkna delta efter att anroparen har muterat det exponerade objektet.

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

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

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

Svar #13

Du kan enkelt använda en spread-egenskap för att kopiera ett objekt utan referenser. Men var försiktig (se kommentarer), 'kopian' är bara på den lägsta objekt-/array-nivån. Nestlade egenskaper är fortfarande referenser!

Fullständig klon:

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

// => mutate without references:

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

Klona med referenser på andra nivån:

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 stöder faktiskt inte djupa kopior inbyggt. Använd en hjälpfunktion. Till exempel Ramda:

http://ramdajs.com/docs/#clone

Svar #14

const objClone = { ...obj };

Var medveten om att nästa objekt fortfarande kopieras som en referens.

Svar #15

Från denna artikel: Hur man kopierar arrayer och objekt i Javascript av 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;
};

Svar #16

För dem som använder AngularJS finns det också en direkt metod för att klona eller utöka objekten i detta bibliotek.

var destination = angular.copy(source);

eller

angular.copy(source, destination);

Mer i dokumentationen för angular.copy...

Svar #17

Prestanda

Idag 2020-04-30 utför jag tester av valda lösningar på Chrome v81.0, Safari v13.1 och Firefox v75.0 på MacOs High Sierra v10.13.6.

Jag fokuserar på hastigheten för kopiering av DATA (objekt med fält av enkla typer, inte metoder osv.). Lösningarna A-I kan endast göra en ytlig kopiering, lösningarna J-U kan göra en djup kopiering.

Resultat för ytlig kopia

  • lösning {...obj} (A) är snabbast på Chrome och Firefox och medelhastig på Safari
  • lösning baserad på Object.assign (B) är snabb på alla webbläsare
  • jQuery (E) och lodash (F,G,H) lösningar är medel/ ganska snabba
  • lösning JSON.parse/stringify (K) är ganska långsam
  • lösningarna D och U är långsamma på alla webbläsare

Resultat för djup kopia

  • lösning Q är snabbast på alla webbläsare
  • jQuery (L) och lodash (J) är medelhastiga
  • lösning JSON.parse/stringify (K) är ganska långsam
  • lösning U är långsammast på alla webbläsare
  • lodash (J) och lösning U kraschar på Chrome för objekt 1000 nivåer djupa

Detaljer

För valda lösningar: A B C(min) D E F G H I J K L M N O P Q R S T U, utför jag 4 tester

  • grund-liten: objekt med 10 icke-nästade fält - du kan köra det HÄR
  • shallow-big: objekt med 1000 icke-nästade fält - du kan köra det HÄR
  • djup-liten: objekt med 10 nivåer-nästade fält - du kan köra det HÄR
  • djup-stor: objekt med 1000 nivåer-inkapslade fält - du kan köra det HÄR

Objekt som används i tester visas i nedanstående kodavsnitt

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

Nedanstående kodsnutt visar testade lösningar och visar skillnader mellan dem

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/[email /lodash.min.js"></script>

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

Nedan finns exempelresultat för Chrome för grunt-stort objekt

Svar #18

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

Svar #19

A.Levys svar är nästan komplett, här är mitt lilla bidrag: det finns ett sätt att hantera rekursiva referenser, se denna rad

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

Om objektet är ett XML DOM-element måste vi istället använda cloneNode

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

Inspirerad av A. Levys omfattande studie och Calvins prototyptillvägagångssätt, erbjuder jag denna lösning:

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

Se även Andy Burkes anteckning i svaren.

Svar #20

Använder Lodash:

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

Svar #21

I ES-6 kan du enkelt använda Object.assign(...). Ex:

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

En bra referens finns här: https://googlechrome.github.io/samples/object-assign-es6/

Svar #22

Intresserad av att klona enkla objekt:

JSON.parse(JSON.stringify(json_original));

Källa: Hur kopierar man ett JavaScript-objekt till en ny variabel UTAN att göra det via referens?

Svar #23

Strukturerad kloning

Uppdatering 2022: Den globala funktionen structuredClone() är redan tillgänglig i Node 17, Deno 1.14 och de flesta större webbläsare (se Can I Use).

Du kan använda samma strukturerade klonmekanism som HTML-standarden inkluderar för att skicka data mellan domäner.

const clone = structuredClone(original);

Se det andra svaret för fler detaljer.

Svar #24

let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

ES6-lösning om du vill (ytligt) klona en klassinstans och inte bara ett egenskapsobjekt.

Svar #25

Du kan klona ett objekt och ta bort alla referenser från det föregående med en enda kodrad. Gör helt enkelt så här:

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

För webbläsare/motorer som för närvarande inte stöder Object.create kan du använda denna 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();
    };
}

Svar #26

Nytt svar på en gammal fråga! Om du har nöjet att använda ECMAScript 2016 (ES6) med Spread Syntax är det enkelt.

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

Detta ger en ren metod för en ytlig kopia av ett objekt. Att göra en djup kopia, vilket innebär att skapa en ny kopia av varje värde i varje rekursivt nästlat objekt, kräver en av de tyngre lösningarna ovan.

JavaScript fortsätter att utvecklas.

Svar #27

Jag tror att det finns ett enkelt och fungerande svar. Vid djupkopiering finns det två frågor:

  1. Håll egenskaperna oberoende av varandra.
  2. Och håll metoderna vid liv på den klonade objektet.

Så jag tror att en enkel lösning är att först serialisera och deserialisera och sedan göra en tilldelning på det för att kopiera funktioner också.

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

Även om denna fråga har många svar, hoppas jag att detta också hjälper.

Svar #28

För en djup kopia och klon, använd JSON.stringify och sedan JSON.parse på objektet:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

Svar #29

(Följande var huvudsakligen en integration av @Maciej Bukowski, @A. Levy, @Jan Turoň, @Redus svarar och @LeviRoberts, @RobGs kommentarer, stort tack till dem!!!)

Djup kopia? — JA! (för det mesta);
Ytlig kopia? — NEJ! (utom Proxy).

Jag välkomnar uppriktigt alla att testa clone().
Dessutom är defineProp() utformat för att enkelt och snabbt (åter)definiera eller kopiera vilken typ av beskrivare som helst.

Funktion

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

  const seen = new WeakMap()

  return clone(object)


  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:
        _object = copyFn(object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

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

              case "AsyncFunction":
              case "GeneratorFunction":
              case "AsyncGeneratorFunction":
                _object = copyFn(object)
                break

              default:
                _object = object
            }
            break

          case "[object Undefined]":          // `Object.create(null)`
            _object = cloneObject(object)
            break

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

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


function convertFnToStr(fn) {
  let fnStr = String(fn)
  if (fn.name.startsWith("[")) // isSymbolKey
    fnStr = fnStr.replace(/\[Symbol\..+?\]/, '')
  fnStr = /^(?!(async )?(function\b|[^{]+?=>))[^(]+?\(/.test(fnStr)
    ? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr
  return fnStr
}

function copyFn(fn) {
  const newFn = new Function(`return ${convertFnToStr(fn)}`)()
  copyPropDescs(newFn, fn)
  return newFn
}



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

// Tester

const obj0 = {
  u: undefined,
  nul: null,
  t: true,
  num: 9,
  str: "",
  sym: Symbol("symbol"),
  [Symbol("e")]: Math.E,
  arr: [[0], [1, 2]],
  d: new Date(),
  re: /f/g,
  get g() { return 0 },
  o: {
    n: 0,
    o: { f: function (...args) { } }
  },
  f: {
    getAccessorStr(object) {
      return []
        .concat(...
          Object.values(Object.getOwnPropertyDescriptors(object))
            .filter(desc => desc.writable === undefined)
            .map(desc => Object.values(desc))
        )
        .filter(prop => typeof prop === "function")
        .map(String)
    },
    f0: function f0() { },
    f1: function () { },
    f2: a => a / (a + 1),
    f3: () => 0,
    f4(params) { return param => param + params },
    f5: (a, b) => ({ c = 0 } = {}) => a + b + c
  }
}

defineProp(obj0, "s", { set(v) { this._s = v } })
defineProp(obj0.arr, "tint", { value: { is: "non-enumerable" } })
obj0.arr[0].name = "nested array"


let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0) { return a + b }
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s", { set(v) { this._s = v + 1 } })
defineProp(obj1.re, "multiline", { value: true })

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Routinely")

console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()

console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()

console.log("obj0\n ",
  ".arr.tint:", obj0.arr.tint, "\n ",
  ".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
  ".arr.tint:", obj1.arr.tint, "\n ",
  ".arr[0].name:", obj1.arr[0].name
)
console.log()

console.log("Accessor-type descriptor\n ",
  "of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
  "of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
  "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
  "  → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)

console.log("—— obj0 has not been interfered.")

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - More kinds of functions")

const fnsForTest = {
  f(_) { return _ },
  func: _ => _,
  aFunc: async _ => _,
  async function() { },
  async asyncFunc() { },
  aFn: async function () { },
  *gen() { },
  async *asyncGen() { },
  aG1: async function* () { },
  aG2: async function* gen() { },
  *[Symbol.iterator]() { yield* Object.keys(this) }
}

console.log(Reflect.ownKeys(fnsForTest).map(k =>
  `${String(k)}:
  ${fnsForTest[k].name}-->
    ${String(fnsForTest[k])}`
).join("\n"))

const normedFnsStr = `{
  f: function f(_) { return _ },
  func: _ => _,
  aFunc: async _ => _,
  function: async function() { },
  asyncFunc: async function asyncFunc() { },
  aFn: async function () { },
  gen: function* gen() { },
  asyncGen: async function* asyncGen() { },
  aG1: async function* () { },
  aG2: async function* gen() { },
  [Symbol.iterator]: function* () { yield* Object.keys(this) }
}`

const copiedFnsForTest = clone(fnsForTest)
console.log("fnsForTest:", fnsForTest)
console.log("fnsForTest (copied):", copiedFnsForTest)
console.log("fnsForTest (normed str):", eval(`(${normedFnsStr})`))
console.log("Comparison of fnsForTest and its clone:",
  Reflect.ownKeys(fnsForTest).map(k =>
    [k, fnsForTest[k] === copiedFnsForTest[k]]
  )
)

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Circular structures")

obj0.o.r = {}
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr

obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)

console.log("Clear obj0's recursion:",
  obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
  "obj0\n ",
  ".o.r:", obj0.o.r, "\n ",
  ".arr:", obj0.arr
)
console.log(
  "obj1\n ",
  ".o.r:", obj1.o.r, "\n ",
  ".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")


console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Classes")

class Person {
  constructor(name) {
    this.name = name
  }
}

class Boy extends Person { }
Boy.prototype.sex = "M"

const boy0 = new Boy
boy0.hobby = { sport: "spaceflight" }

const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"

boy0.name = "one"
boy1.name = "neo"

console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
  Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)

Referenser

  1. Object.create() | MDN
  2. Object.defineProperties() | MDN
  3. Uppräkningsbarhet och ägande av egenskaper | MDN
  4. TypeError: cykliskt objektvärde | MDN

Språktrick som används

  1. Villkorligt lägga till egenskap till objekt

Svar #30

Använd lodash _.cloneDeep().

Ytlig kopia: lodash _.clone()

En ytlig kopia kan göras genom att helt enkelt kopiera referensen.

let obj1 = {
    a: 0,
    b: {
        c: 0,
        e: {
            f: 0
        }
    }
};
let obj3 = _.clone(obj1);
obj1.a = 4;
obj1.b.c = 4;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
//{"a":4,"b":{"c":4,"e":{"f":100}}}

console.log(JSON.stringify(obj3));
//{"a":0,"b":{"c":4,"e":{"f":100}}}

Djupkopiering: lodash _.cloneDeep()

fält avrefereras: istället för att referenser till objekt kopieras

let obj1 = {
    a: 0,
    b: {
        c: 0,
        e: {
            f: 0
        }
    }
};
let obj3 = _.cloneDeep(obj1);
obj1.a = 100;
obj1.b.c = 100;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
{"a":100,"b":{"c":100,"e":{"f":100}}}

console.log(JSON.stringify(obj3));
{"a":0,"b":{"c":0,"e":{"f":0}}}

Relaterade artiklar