Pytanie Jaki jest najskuteczniejszy sposób na głęboki klon obiektu w JavaScript?


Jaki jest najbardziej skuteczny sposób klonowania obiektu JavaScript? widziałem obj = eval(uneval(o)); używane, ale to niestandardowe i obsługiwane tylko przez Firefoksa.

 Robiłem takie rzeczy obj = JSON.parse(JSON.stringify(o)); ale kwestionuj wydajność.

 Widziałem także funkcje kopiowania rekurencyjnego z różnymi wadami.
Jestem zaskoczony, że nie istnieje żadne rozwiązanie kanoniczne.


4548
2018-06-06 14:59


pochodzenie


Eval nie jest zły. Używanie eval słabo. Jeśli obawiasz się skutków ubocznych, używasz go źle. Skutki uboczne, których się obawiasz, są powodem, aby z niego korzystać. Czy ktoś przy okazji właściwie odpowiedział na twoje pytanie? - Prospero
Klonowanie obiektów jest trudnym zadaniem, zwłaszcza w przypadku niestandardowych obiektów dowolnych kolekcji. Które prawdopodobnie dlatego nie ma na to ochoty. - b01
eval() jest ogólnie zły pomysł, ponieważ wiele optymalizatorów silnika JavaScript musi się wyłączyć, gdy mamy do czynienia ze zmiennymi ustawionymi za pośrednictwem eval. Właśnie eval() w twoim kodzie może prowadzić do gorszej wydajności. - user568458
Możliwy duplikat Najbardziej elegancki sposób na sklonowanie obiektu JavaScript - John Slegers
Oto porównanie wydajności najpopularniejszych typów obiektów do klonowania: jsben.ch/#/t917Z - EscapeNetscape


Odpowiedzi:


Uwaga: Jest to odpowiedź na inną odpowiedź, a nie właściwa odpowiedź na to pytanie. Jeśli chcesz szybko klonować obiekty, wykonaj następujące czynności Porada Corbana w ich odpowiedzi na to pytanie.


Chcę zauważyć, że .clone() metoda w jQuery tylko elementy DOM klonów. Aby sklonować obiekty JavaScript, wykonaj:

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

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

Więcej informacji można znaleźć w dokumentacja jQuery.

Chcę również zauważyć, że głęboka kopia jest w rzeczywistości o wiele mądrzejsza niż to, co pokazano powyżej - jest w stanie uniknąć wielu pułapek (na przykład próbując rozszerzyć element DOM). Jest często używany w rdzeniu jQuery i wtyczkach, by uzyskać świetny efekt.


4067



Dla tych, którzy nie zdawali sobie sprawy, odpowiedź Johna Resiga prawdopodobnie miała być rodzajem odpowiedzi / wyjaśnienia Odpowiedź ConroyPzamiast bezpośredniej odpowiedzi na pytanie. - S. Kirby
@ ThiefMaster github.com/jquery/jquery/blob/master/src/core.js w linii 276 (jest trochę kodu, który robi coś innego, ale jest tam kod "jak to zrobić w JS" :) - Rune FS
Oto kod JS za głęboką kopią jQuery dla wszystkich zainteresowanych: github.com/jquery/jquery/blob/master/src/core.js#L265-327 - Alex W
Woah! Aby być super-jasnym: nie mam pojęcia, dlaczego ta odpowiedź została wybrana jako właściwa odpowiedź, była to odpowiedź na poniższe odpowiedzi: stackoverflow.com/a/122190/6524 (który był polecający .clone(), który nie jest odpowiednim kodem do użycia w tym kontekście). Niestety to pytanie przeszło tyle zmian, że pierwotna dyskusja nie jest już nawet widoczna! Proszę postępować zgodnie z radą Corbana i napisać pętlę lub skopiować właściwości bezpośrednio do nowego obiektu, jeśli zależy Ci na szybkości. Lub przetestuj to dla siebie! - John Resig
Jak zrobiłby to bez korzystania z jQuery? - Awesomeness01


Sprawdź ten benchmark: http://jsben.ch/#/bWfk9

W moich poprzednich testach, w których prędkość była głównym problemem, znalazłem

JSON.parse(JSON.stringify(obj))

być najszybszym sposobem na głębszy klon obiektu (bije jQuery.extend z głęboką flagą ustawioną na 10-20%).

jQuery.extend jest dość szybki, gdy flaga głęboka jest ustawiona na false (płytki klon). Jest to dobra opcja, ponieważ zawiera dodatkową logikę do sprawdzania poprawności typu i nie kopiuje ponad niezdefiniowanych właściwości itp., Ale spowolni to nieco.

Jeśli znasz strukturę obiektów, które próbujesz sklonować lub unikasz zagnieżdżonych tablic, możesz napisać proste for (var i in obj) Pętla do sklonowania obiektu podczas sprawdzania hasOwnProperty i będzie znacznie szybsza niż jQuery.

Na koniec, jeśli próbujesz sklonować znaną strukturę obiektu w gorącej pętli, możesz uzyskać DUŻO WIĘKSZEJ WYDAJNOŚCI, włączając po prostu procedurę klonowania i ręcznie konstruując obiekt.

Mechanizmy śledzące JavaScript ssą przy optymalizacji for..in pętle i sprawdzanie hasOwnProperty spowolni również ciebie. Klon ręczny, gdy prędkość jest absolutną koniecznością.

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

Uważaj przy użyciu JSON.parse(JSON.stringify(obj)) metoda włączona Date obiekty - JSON.stringify(new Date()) zwraca ciąg znaków reprezentujący datę w formacie ISO, który JSON.parse()  nie robi przekonwertować z powrotem na Date obiekt. Zobacz tę odpowiedź, aby uzyskać więcej informacji.

Pamiętaj też, że w wersji 65 co najmniej natywne klonowanie nie jest rozwiązaniem. Według ten JSPerf, wykonywanie natywnego klonowania poprzez utworzenie nowej funkcji jest prawie 800x wolniej niż przy użyciu JSON.stringify, który jest niewiarygodnie szybki na całej tablicy.


1877



@trysis Object.create nie klonuje obiektu, używa obiektu prototypowego ... jsfiddle.net/rahpuser/yufzc1jt/2 - rahpuser
Ta metoda usunie również keys od twojego object, które mają functions jako ich wartości, ponieważ JSON nie obsługuje funkcji. - Karlen Kishmiryan
Należy również pamiętać, że za pomocą JSON.parse(JSON.stringify(obj)) na Date Objects również konwertuje datę z powrotem na UTC w reprezentacji ciągów w ISO8601 format. - dnlgmzddr
Podejście JSON również dławi się na kołowych odniesieniach. - rich remer
@velop, Object.assign ({}, objToClone) wygląda na to, że robi płytki klon - używając go podczas odtwarzania w konsoli narzędzi dewelopera klon obiektu nadal wskazywał na odniesienie sklonowanego obiektu. Więc nie sądzę, że to naprawdę ma zastosowanie tutaj. - Garrett Simpson


Zakładając, że masz w obiekcie tylko zmienne i nie ma żadnych funkcji, możesz po prostu użyć:

var newObject = JSON.parse(JSON.stringify(oldObject));

402



con tego podejścia, jak właśnie stwierdziłem, jeśli twój obiekt ma jakieś funkcje (moje ma wewnętrzne gettery i ustawiacze), to są one tracone, gdy są uszeregowane. Jeśli to wszystko czego potrzebujesz ta metoda jest w porządku. - Markive
@Jason, Powodem, dla którego ta metoda jest wolniejsza niż płytkie kopiowanie (na głębokim obiekcie), jest to, z definicji, głęboka kopia. Lecz odkąd JSON jest zaimplementowany w natywnym kodzie (w większości przeglądarek), będzie to znacznie szybsze niż przy użyciu dowolnego innego rozwiązania do kopiowania w oparciu o javascript i może czasami być szybsze niż technika płytkiego kopiowania javascript (patrz: jsperf.com/cloning-an-object/79). - MiJyn
JSON.stringify({key: undefined}) //=> "{}" - Web_Designer
ta technika zniszczy także wszystkich Date obiekty, które są przechowywane wewnątrz obiektu, przekształcając je w ciąg znaków. - fstab
Nie uda się skopiować niczego, co nie jest częścią specyfikacji JSON (json.org) - cdmckay


Strukturalne klonowanie

Definiuje HTML5 wewnętrzny "uporządkowany" algorytm klonowania które mogą tworzyć głębokie klony obiektów. Wciąż jest ograniczony do niektórych wbudowanych typów, ale oprócz kilku typów obsługiwanych przez JSON obsługuje także daty, regeksy, mapy, zestawy, bloki, listy plików, dane obrazu, rzadkie tablice, Wpisane tablicei prawdopodobnie więcej w przyszłości. Zachowuje również odniesienia w sklonowanych danych, umożliwiając obsługę struktur cyklicznych i rekursywnych, które powodowałyby błędy w JSON.

Bezpośrednia pomoc w przeglądarkach: już wkrótce?

Przeglądarki obecnie nie oferują bezpośredniego interfejsu dla algorytmu klonowania strukturalnego, ale globalnego structuredClone() funkcja jest aktywnie dyskutowana w whatwg / html # 793 na GitHub i może wkrótce! Zgodnie z propozycją, użycie go do większości celów będzie tak proste, jak:

const clone = structuredClone(original);

Dopóki tego nie zrobimy, ustrukturyzowane implementacje klonów przeglądarek są eksponowane tylko pośrednio.

Asynchroniczne obejście: użyteczne.

Niższym sposobem na utworzenie zorganizowanego klona z istniejącymi interfejsami API jest opublikowanie danych przez jeden port MessageChannels. Drugi port wyemituje message zdarzenie z ustrukturyzowanym klonem załączonego .data. Niestety, słuchanie tych wydarzeń jest z konieczności asynchroniczne, a synchroniczne alternatywy są mniej praktyczne.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Przykład użycia:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Synchroniczne rozwiązania: Okropne!

Nie ma dobrych opcji synchronicznego tworzenia uporządkowanych klonów. Oto kilka niepraktycznych hacków.

history.pushState() i history.replaceState() obydwa tworzą uporządkowany klon pierwszego argumentu i przypisują tę wartość do history.state. Możesz użyć tego do utworzenia strukturalnego klonu dowolnego obiektu takiego jak ten:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

Przykład użycia:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

main();

Choć synchroniczne, może to być bardzo powolne. Powoduje cały nakład związany z manipulowaniem historią przeglądarki. Regularne wywoływanie tej metody może sprawić, że Chrome chwilowo przestanie reagować.

The Notification konstruktor tworzy uporządkowany klon powiązanych z nim danych. Próbuje również wyświetlić użytkownikowi powiadomienie przeglądarki, ale będzie to po cichu zawieść, chyba że użytkownik zażąda zezwolenia na powiadomienie. Jeśli masz pozwolenie na inne cele, natychmiast zamkniemy powiadomienie, które utworzyliśmy.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

Przykład użycia:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.close();
  return n.data;
};

main();


274



@rynah Właśnie przejrzałem specyfikację ponownie i masz rację: history.pushState() i history.replaceState() metody synchronicznie ustawione history.state do ustrukturyzowanego klonu ich pierwszego argumentu. Trochę dziwne, ale działa. Aktualizuję teraz swoją odpowiedź. - Jeremy
To jest takie złe! Ten interfejs API nie ma być używany w ten sposób. - Fardin
Jako facet, który zaimplementował pushState w Firefoksie, czuję dziwną mieszankę dumy i wstrętu w tym hackerze. Dobra robota chłopaki. - Justin L.


Jeśli nie było żadnego wbudowanego, możesz spróbować:

    function clone(obj) {
      if (obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

      if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
      else
        var temp = obj.constructor();

      for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
          obj['isActiveClone'] = null;
          temp[key] = clone(obj[key]);
          delete obj['isActiveClone'];
        }
      }

      return temp;
    }


273



Rozwiązanie JQuery będzie działać dla elementów DOM, ale nie tylko dla dowolnego obiektu. Mootools ma taki sam limit. Szkoda, że ​​nie mieli generycznego "klonu" dla jakiegokolwiek obiektu ... Rozwiązanie rekursywne powinno działać na wszystko. To prawdopodobnie droga. - jschrab
Ta funkcja ulega uszkodzeniu, jeśli klonowany obiekt ma konstruktor wymagający parametrów. Wygląda na to, że możemy go zmienić na "var temp = new Object ()" i działać w każdym przypadku, nie? - Andrew Arnott
Andrew, jeśli zmienisz go na var temp = new Object (), twój klon nie będzie miał tego samego prototypu co oryginalny obiekt. Spróbuj użyć: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); ' - limscoder
Podobnie jak w przypadku odpowiedzi limskodera, zobacz moją odpowiedź poniżej, jak to zrobić bez wywoływania konstruktora: stackoverflow.com/a/13333781/560114 - Matt Browne
W przypadku obiektów, które zawierają odniesienia do pod-części (tj. Sieci obiektów), to nie działa: Jeśli dwa odniesienia wskazują na ten sam pod-obiekt, kopia zawiera dwie różne kopie tego obiektu. A jeśli istnieją referencje rekursywne, funkcja nigdy się nie zakończy (no, przynajmniej nie tak, jak tego chcesz :-) W tych ogólnych przypadkach musisz dodać słownik obiektów już skopiowanych i sprawdzić, czy już to skopiowałeś ... Programowanie jest skomplikowane, gdy używasz prostego języka - virtualnobi


Efektywny sposób klonowania (nie dogłębnego klonowania) obiektu w jednym wierszu kodu

Na Object.assign Metoda jest częścią standardu ECMAScript 2015 (ES6) i spełnia dokładnie to, czego potrzebujesz.

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

Metoda Object.assign () służy do kopiowania wartości wszystkich przeliczalnych właściwości z jednego lub więcej obiektów źródłowych do obiektu docelowego.

Czytaj więcej...

The polyfill do obsługi starszych przeglądarek:

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

132



To nie rekursywnie kopiuje, więc tak naprawdę nie oferuje rozwiązania problemu klonowania obiektu. - mwhite
Ta metoda zadziałała, chociaż testowałem kilka i _.rozmiar ({}, (obj)) był NAJGORSZY najszybszy: 20x szybciej niż JSON.parse i 60% szybciej niż Object.assign, na przykład. Bardzo dobrze kopiuje wszystkie sub-obiekty. - Nico
@mwhite jest różnica między klonem a klonem. Ta odpowiedź w rzeczywistości jest klonowana, ale nie jest głęboka. - Meirion Hughes
op poprosił o głęboki klon. to nie robi głębokiego klona. - user566245
wow tak wiele Object.assign odpowiedzi, nawet nie czytając pytania op ... - martin8768


Kod:

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

Test:

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

91



co powiesz na var obj = {} i obj.a = obj - neaumusic
Nie rozumiem tej funkcji. Przypuszczać from.constructor jest Date na przykład. Jak byłby trzeci if test zostanie osiągnięty, gdy drugi if test zakończy się sukcesem i spowoduje powrót funkcji (od Date != Object && Date != Array)? - Adam McKee
@AdamMcKee Ponieważ przekazywanie argumentów javascript i przypisywanie zmiennych jest trudne. To podejście działa świetnie, w tym daty (które rzeczywiście są obsługiwane przez drugi test) - skrzypce do przetestowania tutaj: jsfiddle.net/zqv9q9c6. - brichins
* Miałem na myśli "przypisanie parametrów" (w ramach treści funkcji), a nie "przypisanie zmiennych". Chociaż pomaga to w dokładnym zrozumieniu. - brichins
@NickSweeting: Spróbuj - może to działa. Jeśli nie, napraw i zaktualizuj odpowiedź. Tak to działa tutaj w społeczności :) - Kamarey


Właśnie tego używam:

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

81



To nie wydaje się właściwe. cloneObject({ name: null }) => {"name":{}} - Niyaz
Wynika to z kolejnej głupiej rzeczy w javascript typeof null > "object" ale Object.keys(null) > TypeError: Requested keys of a value that is not an object. zmień warunek na if(typeof(obj[i])=="object" && obj[i]!=null) - Vitim.us
To przydzieli odziedziczone właściwości przeliczalne obj bezpośrednio do klona i zakłada to obj jest zwykłym Obiektem. - RobG
To powoduje również zbędne tablice, które są konwertowane na obiekty z kluczami numerycznymi. - blade
Nie stanowi problemu, jeśli nie używasz wartości null. - Jorge Bucaran


Głębokie kopiowanie według wydajności: Kategoria od najlepszej do najgorszej

  • Zmiana przypisania "=" (tylko tablice znaków, tablice liczb)
  • Kawałek (tylko tablice sznurkowe, tablice liczbowe - tylko)
  • Konkatenacja (tylko tablice sznurkowe, tablice liczb)
  • Funkcja niestandardowa: pętla for lub kopia rekursywna
  • jQuery $ .extend
  • JSON.parse (tylko tablice sznurkowe, tablice liczbowe, tablice obiektów - tylko)
  • Underscore.js'_klon (tylko tablice znaków, tablice liczbowe - tylko)
  • Lo-Dash's _.cloneDeep

Głęboko kopiuj tablicę ciągów lub liczb (jeden poziom - brak odnośników):

Gdy tablica zawiera liczby i ciągi znaków - funkcje takie jak .slice (), .concat (), .splice (), operator przypisania "=" i funkcja klonowania Underscore.js; zrobi głęboką kopię elementów tablicy.

Gdzie przeniesienie ma największą wydajność:

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

A .slice () ma lepszą wydajność niż .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

Głęboko kopiuj tablicę obiektów (dwa lub więcej poziomów - wskaźniki odniesienia):

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

Napisz niestandardową funkcję (ma wyższą wydajność niż $ .extend () lub 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);

Użyj funkcji narzędziowych innych firm:

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

Gdzie $ .extend jQuery ma lepszą wydajność:


64



Testowałem kilka, a _.extend ({}, (obj)) był NAJGORSZY najszybszy: 20x szybciej niż JSON.parse i 60% szybciej niż Object.assign, na przykład. Bardzo dobrze kopiuje wszystkie sub-obiekty. - Nico
@NicoDurand - Czy Twoje testy wydajności są dostępne online? - tfmontague
Wszystkie twoje przykłady są płytkie, jeden poziom. To nie jest dobra odpowiedź. Pytanie dotyczyło głęboki klonowanie, tj. co najmniej dwa poziomy. - Karl Morrison
Głęboka kopia jest wtedy, gdy obiekt jest kopiowany w całości, bez użycia odnośników do innych obiektów. Techniki w sekcji "Głęboko kopiuj tablicę obiektów", takie jak jQuery.extend () i niestandardowa funkcja (która jest rekursywna) kopiują obiekty z "co najmniej dwoma poziomami". Tak więc nie wszystkie przykłady są kopiami "jednego poziomu". - tfmontague
Podoba mi się twoja funkcja kopiowania niestandardowego, ale powinieneś wykluczyć wartości null, w przeciwnym razie wszystkie wartości null są konwertowane na obiekty, tj .: out[key] = (typeof v === "object" && v !== null) ? copy(v) : v; - josi