Bagaimana cara saya mengkloning objek JavaScript dengan benar?

3079

Saya punya objek x. Saya ingin menyalinnya sebagai objek y, sehingga perubahan ytidak diubah x. Saya menyadari bahwa menyalin objek yang berasal dari objek JavaScript bawaan akan menghasilkan properti tambahan yang tidak diinginkan. Ini bukan masalah, karena saya menyalin salah satu objek saya sendiri yang dibangun secara literal.

Bagaimana cara saya mengkloning objek JavaScript dengan benar?

soundly_typed
sumber
30
Lihat pertanyaan ini: stackoverflow.com/questions/122102/…
Niyaz
256
Untuk JSON, saya menggunakanmObj=JSON.parse(JSON.stringify(jsonObject));
Tuan Loh.
67
Saya benar-benar tidak mengerti mengapa tidak ada yang menyarankan Object.create(o), itu melakukan semua yang penulis minta?
froginvasion
44
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; Setelah melakukan ini, y.deep.keyjuga akan menjadi 2, maka Object.create TIDAK BISA DIGUNAKAN untuk kloning ...
Ruben Stolk
17
@ r3wt itu tidak akan berfungsi ... Silakan posting hanya setelah melakukan tes dasar dari solusi ..
akshay

Jawaban:

1561

Untuk melakukan ini untuk objek apa pun dalam JavaScript tidak akan mudah atau langsung. Anda akan mengalami masalah salah mengambil atribut dari prototipe objek yang harus dibiarkan dalam prototipe dan tidak disalin ke instance baru. Jika, misalnya, Anda menambahkan clonemetode Object.prototype, seperti yang digambarkan beberapa jawaban, Anda harus melewati atribut itu secara eksplisit. Tetapi bagaimana jika ada metode tambahan lain yang ditambahkan Object.prototype, atau prototipe perantara lainnya, yang tidak Anda ketahui? Dalam hal ini, Anda akan menyalin atribut yang seharusnya tidak Anda miliki, jadi Anda perlu mendeteksi atribut non-lokal yang tidak terduga dengan hasOwnPropertymetode ini.

Selain atribut yang tidak dapat dihitung, Anda akan menghadapi masalah yang lebih sulit ketika Anda mencoba menyalin objek yang memiliki properti tersembunyi. Misalnya, prototypeadalah properti tersembunyi dari suatu fungsi. Juga, prototipe objek direferensikan dengan atribut __proto__, yang juga tersembunyi, dan tidak akan disalin oleh perulangan for / in iterating pada atribut objek sumber. Saya pikir __proto__mungkin khusus untuk penerjemah JavaScript Firefox dan mungkin ada sesuatu yang berbeda di browser lain, tetapi Anda mendapatkan gambarnya. Tidak semuanya enumerable. Anda dapat menyalin atribut tersembunyi jika Anda tahu namanya, tetapi saya tidak tahu cara untuk menemukannya secara otomatis.

Namun halangan lain dalam pencarian solusi elegan adalah masalah pengaturan pewarisan prototipe dengan benar. Jika prototipe objek sumber Anda adalah Object, maka cukup membuat objek umum baru dengan {}akan bekerja, tetapi jika prototipe sumber tersebut adalah turunan dari Object, maka Anda akan kehilangan anggota tambahan dari prototipe yang dilewati menggunakan hasOwnPropertyfilter, atau yang berada di prototipe, tetapi tidak bisa dihitung pada awalnya. Salah satu solusinya mungkin dengan memanggil constructorproperti objek sumber untuk mendapatkan objek salinan awal dan kemudian menyalin atribut, tetapi Anda masih tidak akan mendapatkan atribut yang tidak dapat dihitung. Misalnya, Dateobjek menyimpan datanya sebagai anggota tersembunyi:

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

String tanggal untuk d1akan berada 5 detik di belakang string d2. Cara untuk membuat satu Datesama dengan yang lain adalah dengan memanggil setTimemetode, tetapi itu khusus untuk Datekelas. Saya tidak berpikir ada solusi umum anti peluru untuk masalah ini, meskipun saya akan senang salah!

Ketika saya harus menerapkan dalam umum menyalin Saya akhirnya mengorbankan dengan mengasumsikan bahwa saya hanya akan perlu menyalin polos Object, Array, Date, String, Number, atau Boolean. 3 jenis terakhir tidak berubah, jadi saya bisa melakukan salinan dangkal dan tidak khawatir tentang itu berubah. Saya selanjutnya berasumsi bahwa elemen apa pun yang terkandung dalam Objectatau Arrayjuga akan menjadi salah satu dari 6 jenis sederhana dalam daftar itu. Ini dapat dilakukan dengan kode seperti berikut:

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

Fungsi di atas akan bekerja secara memadai untuk 6 tipe sederhana yang saya sebutkan, selama data dalam objek dan array membentuk struktur pohon. Artinya, tidak ada lebih dari satu referensi ke data yang sama di objek. Sebagai contoh:

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

Ini tidak akan dapat menangani objek JavaScript apa pun, tetapi mungkin cukup untuk banyak tujuan selama Anda tidak menganggap bahwa itu hanya akan berfungsi untuk apa pun yang Anda lemparkan padanya.

A. Retribusi
sumber
5
hampir bekerja dengan baik dalam nodejs - hanya harus mengubah baris untuk (var i = 0, var len = obj.length; i <len; ++ i) {to for (var i = 0; i <obj.length; ++ i) {
Trindaz

5
Untuk googler masa depan: salinan dalam yang sama, melewati referensi secara rekursif alih-alih menggunakan pernyataan 'kembali' di gist.github.com/2234277
Trindaz

8
Apakah saat ini JSON.parse(JSON.stringify([some object]),[some revirer function])akan menjadi solusi?
KooiInc

5
Di cuplikan pertama, apakah Anda yakin seharusnya tidak var cpy = new obj.constructor()?
cyon

8
Penugasan objek tampaknya tidak membuat salinan yang benar di Chrome, mempertahankan referensi ke objek asli - akhirnya menggunakan JSON.stringify dan JSON.parse untuk mengkloning - bekerja dengan sempurna
1owk3y

974

Jika Anda tidak menggunakan Dates, fungsi, tidak terdefinisi, regExp atau Infinity dalam objek Anda, liner yang sangat sederhana adalah 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()

Ini berfungsi untuk semua jenis objek yang berisi objek, array, string, boolean, dan angka.

Lihat juga artikel ini tentang algoritma klon terstruktur dari browser yang digunakan saat memposting pesan ke dan dari pekerja. Ini juga berisi fungsi untuk kloning dalam.


42
Perhatikan bahwa ini hanya dapat digunakan untuk pengujian. Pertama, jauh dari optimal dalam hal konsumsi waktu dan memori. Kedua, tidak semua browser memiliki metode ini.
Nux

2
Anda selalu dapat menyertakan JSON2.js atau JSON3.js. Anda tetap membutuhkannya untuk aplikasi Anda. Tapi saya setuju ini mungkin bukan solusi terbaik, karena JSON.stringify tidak termasuk properti yang diwarisi.
Tim Hong

78
@Nux, Mengapa tidak optimal dalam hal waktu dan memori? MiJyn mengatakan: "Alasan mengapa metode ini lebih lambat daripada menyalin dangkal (pada objek yang dalam) adalah bahwa metode ini, menurut definisi, salinan dalam. Tetapi karena JSON diimplementasikan dalam kode asli (di sebagian besar browser), ini akan jauh lebih cepat daripada menggunakan solusi penyalinan dalam berbasis javascript lainnya, dan kadang-kadang mungkin lebih cepat daripada teknik penyalinan dangkal berbasis javascript (lihat: jsperf.com/cloning-an-object/79). " stackoverflow.com/questions/122102/…
BeauCielBleu

16
Saya hanya ingin menambahkan pembaruan untuk ini untuk Oktober 2014. Chrome 37+ lebih cepat dengan JSON.parse (JSON.stringify (oldObject)); Manfaat menggunakan ini adalah sangat mudah bagi mesin javascript untuk melihat dan mengoptimalkan menjadi sesuatu yang lebih baik jika diinginkan.
mirhagk

17
Pembaruan 2016: Sekarang ini harus berfungsi di hampir semua browser yang banyak digunakan. (lihat Dapatkah saya menggunakan ... ) Pertanyaan utama sekarang adalah apakah itu cukup berkinerja.
James Foster

783

Dengan jQuery, Anda dapat menyalin dangkal dengan memperluas :

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

perubahan selanjutnya pada copiedObjecttidak akan mempengaruhi originalObject, dan sebaliknya.

Atau untuk membuat salinan yang dalam :

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

164
atau bahkan:var copiedObject = jQuery.extend({},originalObject);
Grant McLean

82
Juga berguna untuk menentukan true sebagai param pertama untuk salinan dalam:jQuery.extend(true, {}, originalObject);
Will Shaver

6
Ya, saya menemukan tautan ini bermanfaat (solusi yang sama dengan Pascal) stackoverflow.com/questions/122102/…
Garry English

3
Sekadar catatan, ini tidak menyalin proto constructor dari objek asli
Sam Jones

1
Menurut stackoverflow.com/questions/184710/… , sepertinya "copy dangkal" hanya menyalin referensi originalObject, jadi mengapa di sini mengatakan itu ...subsequent changes to the copiedObject will not affect the originalObject, and vice versa.... Maaf saya benar-benar bingung.
Carr

687

Dalam ECMAScript 6 ada metode Object.assign , yang menyalin nilai dari semua properti yang dapat dihitung sendiri dari satu objek ke objek lainnya. Sebagai contoh:

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

Namun perlu diperhatikan bahwa objek bersarang masih disalin sebagai referensi.


Ya, saya percaya itulah Object.assignjalan yang harus ditempuh. Sangat mudah untuk mengisinya

22
Juga menyadari bahwa ini akan menyalin "metode" yang didefinisikan melalui literal objek (karena ini adalah enumerable) tetapi tidak metode menantang melalui mekanisme "kelas" (karena ini tidak enumerable).
Marcus Junius Brutus

16
Saya pikir harus disebutkan bahwa ini tidak didukung oleh IE kecuali Edge. Beberapa orang masih menggunakan ini.
Saulius

1
Ini sama dengan @EugeneTiurin jawabannya.
Layu

5
Berbicara dari pengalaman di sini ... JANGAN gunakan ini. Objek dirancang agar hierarkis dan Anda hanya akan menyalin level pertama, mengarahkan Anda untuk menetapkan seluruh objek yang telah disalin. Percayalah, ini mungkin menghemat berhari-hari Anda dari garukan kepala.
ow3n

232

Per MDN :

  • Jika Anda ingin salinan dangkal, gunakan Object.assign({}, a)
  • Untuk salinan "dalam", gunakan JSON.parse(JSON.stringify(a))

Tidak perlu untuk perpustakaan eksternal tetapi Anda harus memeriksa kompatibilitas browser terlebih dahulu .


8
JSON.parse (JSON.stringify (a)) terlihat cantik, tetapi sebelum menggunakannya, saya sarankan melakukan pembandingan waktu yang dibutuhkan untuk koleksi yang Anda inginkan. Bergantung pada ukuran objek, ini mungkin bukan opsi tercepat sama sekali.
Edza

3
Metode JSON Saya perhatikan mengubah objek tanggal menjadi string tetapi tidak kembali ke tanggal. Harus berurusan dengan kesenangan zona waktu di Javascript dan secara manual memperbaiki tanggal apa pun. Mungkin kasus serupa untuk jenis lain selain tanggal
Steve Seeger

untuk Date, saya akan menggunakan moment.js karena memiliki clonefungsionalitas. lihat lebih lanjut di sini momentjs.com/docs/#/parsing/moment-clone
Tareq

Ini bagus tetapi berhati-hatilah jika objek memiliki fungsi di dalam
lesolorzanov

Masalah lain dengan parsing json adalah referensi melingkar. Jika ada referensi melingkar di dalam objek, ini akan pecah
1919

133

Ada banyak jawaban, tetapi tidak ada yang menyebutkan Object.create dari ECMAScript 5, yang memang tidak memberi Anda salinan persis, tetapi menetapkan sumber sebagai prototipe objek baru.

Jadi, ini bukan jawaban yang tepat untuk pertanyaan itu, tetapi ini adalah solusi satu-baris dan dengan demikian elegan. Dan ini bekerja paling baik untuk 2 kasus:

  1. Di mana warisan seperti itu berguna (ya!)
  2. Di mana objek sumber tidak akan dimodifikasi, sehingga membuat hubungan antara 2 objek tidak menjadi masalah.

Contoh:

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

Mengapa saya menganggap solusi ini lebih unggul? Ini asli, sehingga tidak ada perulangan, tidak ada rekursi. Namun, browser lama akan membutuhkan polyfill.


Catatan: Object.create bukan salinan dalam (itpastorn disebutkan tidak ada rekursi). Bukti:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
zamnuts

103
Ini adalah warisan prototypal, bukan kloning. Ini adalah hal yang sangat berbeda. Objek baru tidak memiliki properti itu sendiri, hanya menunjuk ke properti prototipe. Inti dari kloning adalah untuk membuat objek baru yang tidak mereferensikan properti di objek lain.
13

7
Saya setuju dengan Anda sepenuhnya. Saya setuju juga bahwa ini bukan kloning karena mungkin 'dimaksudkan'. Tapi ayolah orang-orang, rangkul sifat JavaScript alih-alih mencoba menemukan solusi tidak jelas yang tidak terstandarisasi. Tentu, Anda tidak suka prototipe dan semuanya "bla" untuk Anda, tetapi sebenarnya sangat berguna jika Anda tahu apa yang Anda lakukan.
froginvasion

4
@RobG: Artikel ini menjelaskan perbedaan antara referensi dan kloning: en.wikipedia.org/wiki/Cloning_(programming) . Object.createmenunjuk ke properti orang tua melalui referensi. Itu berarti jika nilai properti orang tua berubah, anak juga akan berubah. Ini memiliki beberapa efek samping yang mengejutkan dengan array bersarang dan objek yang dapat menyebabkan bug yang sulit ditemukan dalam kode Anda jika Anda tidak menyadarinya: jsbin.com/EKivInO/2 . Objek yang dikloning adalah objek yang benar-benar baru dan independen yang memiliki properti dan nilai yang sama dengan induknya, tetapi tidak terhubung ke induknya.
d13

1
Ini menyesatkan orang ... Object.create () dapat digunakan sebagai sarana untuk pewarisan tetapi kloning tidak ada di dekat itu.
prajnavantha

128

Cara elegan untuk mengkloning objek Javascript dalam satu baris kode

Sebuah Object.assignmetode adalah bagian dari 2015 standar ECMAScript (ES6) dan melakukan apa yang Anda butuhkan.

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

Metode Object.assign () digunakan untuk menyalin nilai-nilai semua properti enumerable sendiri dari satu atau lebih objek sumber ke objek target.

Baca lebih banyak...

The polyfill untuk mendukung browser lama:

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

Maaf untuk pertanyaan bodoh, tetapi mengapa Object.assignmengambil dua parameter ketika valuefungsi di polyfill hanya mengambil satu parameter?
Qwertie

@ Qwertie kemarin Semua argumen diulang dan digabung menjadi satu objek, memprioritaskan properti dari arg yang terakhir disahkan
Eugene Tiurin

Oh saya mengerti, terima kasih (saya tidak terbiasa dengan argumentsobjek sebelumnya.) Saya mengalami kesulitan menemukan Object()melalui Google ... ini adalah tipografi, bukan?
Qwertie

44
ini hanya akan melakukan "kloning" yang dangkal
Marcus Junius Brutus

1
Jawaban ini persis sama dengan ini: stackoverflow.com/questions/122102/... Saya tahu itu oleh orang yang sama tetapi Anda harus referensi daripada hanya menyalin jawaban.
lesolorzanov

87

Ada beberapa masalah dengan sebagian besar solusi di internet. Jadi saya memutuskan untuk membuat tindak lanjut, yang mencakup, mengapa jawaban yang diterima tidak boleh diterima.

situasi mulai

Saya ingin menyalin dalam- dalam Javascript Objectdengan semua anak-anak dan anak-anak mereka dan sebagainya. Tapi karena saya bukan jenis pengembang normal, saya Objectpunya yang normal properties , circular structuresdan bahkan nested objects.

Jadi mari kita buat circular structuredan yang nested objectpertama.

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

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

Mari kita kumpulkan semuanya dalam sebuah Objectnama a.

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

Selanjutnya, kami ingin menyalin ake variabel bernama bdan memutasikannya.

var b = a;

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

Anda tahu apa yang terjadi di sini karena jika tidak Anda bahkan tidak akan mendarat pada pertanyaan besar ini.

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

Sekarang mari kita cari solusinya.

JSON

Upaya pertama yang saya coba gunakan JSON.

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

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

Jangan buang terlalu banyak waktu untuk itu, Anda akan mendapatkannya TypeError: Converting circular structure to JSON.

Salinan rekursif ("jawaban" yang diterima)

Mari kita lihat jawaban yang diterima.

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

Terlihat bagus, heh? Ini adalah salinan objek berulang dan menangani tipe lain juga, seperti Date, tapi itu bukan keharusan.

var b = cloneSO(a);

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

Rekursi dan circular structurestidak bekerja dengan baik bersama ...RangeError: Maximum call stack size exceeded

solusi asli

Setelah berdebat dengan rekan kerja saya, bos saya bertanya kepada kami apa yang terjadi, dan dia menemukan solusi sederhana setelah beberapa googling. Itu disebut Object.create.

var b = Object.create(a);

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

Solusi ini ditambahkan ke Javascript beberapa waktu lalu dan bahkan menangani circular structure.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

... dan Anda lihat, itu tidak berfungsi dengan struktur bersarang di dalam.

polyfill untuk solusi asli

Ada polyfill Object.createdi browser lama seperti IE 8. Ini sesuatu seperti yang direkomendasikan oleh Mozilla, dan tentu saja, itu tidak sempurna dan menghasilkan masalah yang sama dengan solusi asli .

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

var b = clonePF(a);

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

Saya telah menempatkan di Fluar ruang lingkup sehingga kita dapat melihat apa yang instanceofmemberitahu kita.

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

Masalah yang sama dengan solusi asli , tetapi output sedikit lebih buruk.

solusi yang lebih baik (tetapi tidak sempurna)

Ketika menggali sekitar, saya menemukan pertanyaan serupa ( dalam Javascript, ketika melakukan salinan yang dalam, bagaimana saya menghindari siklus, karena properti menjadi "ini"? ) Untuk yang ini, tetapi dengan solusi yang jauh lebih baik.

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

Dan mari kita lihat outputnya ...

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

Persyaratannya cocok, tetapi masih ada beberapa masalah yang lebih kecil, termasuk mengubah instancedari nesteddan circke Object.

Struktur pohon yang berbagi daun tidak akan disalin, mereka akan menjadi dua daun independen:

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

kesimpulan

Solusi terakhir menggunakan rekursi dan cache, mungkin tidak menjadi yang terbaik, tapi itu nyata deep-salinan objek. Menangani sederhana properties, circular structuresdan nested object, tetapi itu akan mengacaukan contoh mereka saat kloning.

jsfiddle


12
jadi kesimpulannya adalah untuk menghindari masalah itu :)
mikus

@mikus sampai ada spesifikasi nyata yang mencakup lebih dari sekadar kasing biasa, ya.
Fabio Poloni

2
Analisis yang baik dari solusi yang diberikan di atas tetapi kesimpulan yang ditarik oleh penulis menunjukkan bahwa tidak ada solusi untuk pertanyaan ini.
Amir Mog

2
Sayang sekali bahwa JS tidak termasuk fungsi klon asli.
l00k

1
Di antara semua jawaban teratas, saya merasa ini dekat dengan jawaban yang benar.
KTU

77

Jika Anda setuju dengan salinan yang dangkal, perpustakaan underscore.js memiliki metode klon .

y = _.clone(x);

atau Anda dapat memperpanjang seperti

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

2
Terima kasih. Menggunakan teknik ini di server Meteor.
Turbo

Untuk memulai dengan cepat dengan lodash, saya akan merekomendasikan belajar npm, Browserify, serta lodash. Saya dapat klon untuk bekerja dengan 'npm i - save lodash.clone' dan kemudian 'var clone = require (' lodash.clone ');' Untuk mulai bekerja, Anda perlu sesuatu seperti browserify. Setelah Anda menginstalnya dan mempelajari cara kerjanya, Anda akan menggunakan 'browserify yourfile.js> bundle.js; mulai chrome index.html' setiap kali Anda menjalankan kode Anda (bukannya langsung masuk ke Chrome). Ini mengumpulkan file Anda dan semua file yang Anda butuhkan dari modul npm ke bundle.js. Anda mungkin dapat menghemat waktu dan mengotomatiskan langkah ini dengan Gulp.
Aaron Bell

65

OK, bayangkan Anda memiliki objek di bawah ini dan Anda ingin mengkloningnya:

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

atau

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

jawabannya adalah terutama depeneds yang ECMAScript Anda gunakan, dalam ES6+, Anda hanya dapat menggunakan Object.assignuntuk melakukan clone:

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

atau menggunakan operator spread seperti ini:

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

Tetapi jika Anda menggunakan ES5, Anda dapat menggunakan beberapa metode, tetapi JSON.stringify, pastikan Anda tidak menggunakan sebagian besar data untuk menyalin, tetapi ini bisa menjadi satu cara yang praktis dalam banyak kasus, seperti ini:

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

Bisakah Anda memberi contoh apa yang big chunk of dataakan disamakan? 100kb? 100MB? Terima kasih!
user1063287

Ya, @ user1063287, bahwa pada dasarnya data yang lebih besar, kinerjanya lebih buruk ... jadi itu benar-benar tergantung, bukan kb, mb atau gb, ini lebih tentang berapa kali Anda ingin melakukan itu juga ... Juga tidak akan bekerja untuk fungsi dan barang-barang lainnya ...
Alireza

3
Object.assignmembuat salinan yang dangkal (seperti halnya spread, @Alizera)
Bogdan D

Anda tidak dapat menggunakan let in es5: ^) @Alireza
SensationSama

40

Salah satu solusi yang tidak berlaku adalah dengan menggunakan pengkodean JSON untuk membuat salinan dalam objek yang tidak memiliki metode anggota. Metodologinya adalah untuk menyandikan JSON objek target Anda, kemudian dengan mendekodekannya, Anda mendapatkan salinan yang Anda cari. Anda dapat memecahkan kode sebanyak yang Anda inginkan untuk membuat salinan sebanyak yang Anda butuhkan.

Tentu saja, fungsi bukan milik JSON, jadi ini hanya berfungsi untuk objek tanpa metode anggota.

Metodologi ini sangat cocok untuk kasus penggunaan saya, karena saya menyimpan gumpalan JSON di penyimpanan nilai kunci, dan ketika mereka diekspos sebagai objek dalam JavaScript API, setiap objek sebenarnya berisi salinan keadaan asli objek sehingga kami dapat menghitung delta setelah penelepon memutasi objek yang terbuka.

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

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

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

Mengapa fungsi bukan milik JSON? Saya telah melihat mereka ditransfer sebagai JSON lebih dari sekali ...
the_drow

5
Fungsi bukan bagian dari spesifikasi JSON karena itu bukan cara yang aman (atau pintar) untuk mentransfer data, yang merupakan tujuan JSON dibuat. Saya tahu enkoder JSON asli di Firefox mengabaikan fungsi yang diteruskan ke sana, tapi saya tidak yakin dengan perilaku orang lain.
Kris Walker

1
@mark: { 'foo': function() { return 1; } }adalah objek yang dikonstruksi secara literal.
abarnert

Fungsi @abarnert bukan data. "Function literals" adalah nama yang salah - karena fungsi dapat berisi kode arbitrer, termasuk penugasan dan segala macam hal yang "tidak dapat serial".
vemv

35

Anda cukup menggunakan properti sebaran untuk menyalin objek tanpa referensi. Tapi hati-hati (lihat komentar), 'copy' hanya pada level objek / array terendah. Properti bersarang masih referensi!


Klon lengkap:

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

// => mutate without references:

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

Klon dengan referensi di tingkat kedua:

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 sebenarnya tidak mendukung klon dalam secara asli. Gunakan fungsi utilitas. Misalnya Ramda:

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


1
Ini tidak berfungsi ... itu akan bekerja mungkin ketika x akan menjadi sebuah array misalnya x = ['ab', 'cd', ...]
Kamil Kiełczewski

3
Ini berfungsi, tetapi ingatlah ini adalah salinan SHALLOW, oleh karena itu referensi mendalam ke objek lain tetap menjadi referensi!
Bugs Bunny

Klon sebagian juga dapat terjadi dengan cara ini:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
Cristian Traìna

25

Bagi mereka yang menggunakan AngularJS, ada juga metode langsung untuk kloning atau memperluas objek di perpustakaan ini.

var destination = angular.copy(source);

atau

angular.copy(source, destination);

Lebih banyak di dokumentasi angular.copy ...


2
Ini adalah salinan FYI yang dalam.
zamnuts

22

Ini adalah fungsi yang bisa Anda gunakan.

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

10
Jawaban ini cukup dekat, tetapi tidak sepenuhnya benar. Jika Anda mencoba mengkloning objek Date, Anda tidak akan mendapatkan tanggal yang sama karena panggilan ke fungsi konstruktor Date menginisialisasi Date baru dengan tanggal / waktu saat ini. Nilai itu tidak dapat dihitung dan tidak akan disalin oleh for / in loop.
A. Levy

Tidak sempurna, tapi bagus untuk kasus dasar itu. Misalnya memungkinkan kloning sederhana dari argumen yang bisa menjadi Objek dasar, Array atau String.
james_womack

Terpilih untuk memanggil konstruktor dengan benar menggunakan new. Jawaban yang diterima tidak.
GetFree

bekerja pada node yang lainnya! masih meninggalkan tautan referensi
user956584

Pemikiran rekursif itu hebat. Tetapi jika nilainya array, itu akan berhasil?
Q10Viking

22

Jawaban A.Levy hampir selesai, inilah kontribusi kecil saya: ada cara bagaimana menangani referensi rekursif , lihat baris ini

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

Jika objeknya adalah elemen XML DOM, kita harus menggunakan cloneNode sebagai gantinya

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

Terinspirasi oleh studi lengkap A.Levy dan pendekatan prototyping Calvin, saya menawarkan solusi ini:

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

Lihat juga catatan Andy Burke dalam jawabannya.


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

22

Dari artikel ini: Cara menyalin array dan objek dalam Javascript oleh 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;
};

4
Ini dekat, tetapi tidak berfungsi untuk objek apa pun. Coba kloning objek Date dengan ini. Tidak semua properti dapat dihitung, sehingga tidak semua akan ditampilkan di for / in loop.
A. Levy

Menambah prototipe objek seperti ini memecah jQuery untuk saya. Bahkan ketika saya berganti nama menjadi clone2.
iPadDeveloper2011

3
@ iPadDeveloper2011 Kode di atas memiliki bug di dalamnya tempat ia membuat variabel global yang disebut 'i' '(untuk i dalam hal ini)', daripada '(untuk var i dalam hal ini)'. Saya memiliki cukup karma untuk diedit dan diperbaiki lalu saya lakukan.
mikemaccana

1
@ Calvin: ini harus dibuat properti yang tidak dapat dihitung, jika tidak, 'klon' akan muncul di loop 'untuk'.
mikemaccana

2
mengapa bukan var copiedObj = Object.create(obj);cara yang bagus juga?
Dan P.

19

Di ES-6 Anda cukup menggunakan Object.assign (...). Ex:

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

Referensi yang baik ada di sini: https://googlechrome.github.io/samples/object-assign-es6/


12
Itu tidak mendalam mengkloning objek.
Agustus

Itu tugas, bukan salinan. clone.Title = "just a clone" berarti obj.Title = "just a clone".
HoldOffHunger

@HoldOffHunger Anda salah. Periksa di konsol JS browser Anda ( let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";)
collapsar

@collapsar: Itulah yang saya periksa, maka console.log (orang) akan menjadi "Whazzup", bukan "Thor Odinson". Lihat komentar Agustus.
HoldOffHunger

1
@HoldOffHunger Tidak terjadi di Chrome 60.0.3112.113 atau di Edge 14.14393; Komentar Agustus tidak berlaku karena nilai tipe objproperti primitif memang dikloning. Nilai properti yang merupakan objek itu sendiri tidak akan dikloning.
collapsar

18

Dalam ECMAScript 2018

let objClone = { ...obj };

Sadarilah bahwa objek bersarang masih disalin sebagai referensi.


1
Terima kasih atas petunjuk bahwa objek bersarang masih disalin sebagai referensi! Saya hampir menjadi gila ketika men-debug kode saya karena saya memodifikasi properti bersarang pada "klon" tetapi yang asli diubah.
Benny Neugebauer

Ini ES2016, bukan 2018, dan jawaban ini diberikan dua tahun sebelumnya .
Dan Dascalescu

jadi bagaimana jika saya ingin salinan properti bersarang juga
Sunil Garg

1
@SunilGarg Untuk menyalin properti bersarang juga Anda dapat menggunakan const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan Garre

16

Menggunakan Lodash:

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

5
OMG itu gila untuk menemukan kembali kloning. Ini satu-satunya jawaban yang waras.
Dan Ross

5
Saya lebih suka _.cloneDeep(x)karena pada dasarnya adalah hal yang sama seperti di atas, tetapi membaca lebih baik.
garbanzio


13

Anda dapat mengkloning suatu objek dan menghapus referensi apa pun dari yang sebelumnya menggunakan satu baris kode. Cukup lakukan:

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

Untuk browser / mesin yang saat ini tidak mendukung Object.create Anda dapat menggunakan polyfill ini:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}

1
+1 Object.create(...)tampaknya pasti cara yang harus ditempuh.
René Nyffenegger

Jawaban sempurna. Mungkin Anda bisa menambahkan penjelasan Object.hasOwnProperty? Dengan begitu orang tahu cara mencegah mencari tautan prototipe.
froginvasion

Berfungsi dengan baik tetapi browser apa yang bekerja dengan polyfill?
Ian Lunn

11
Ini menciptakan obj2 dengan obj1 sebagai prototipe. Ini hanya berfungsi karena Anda membayangi textanggota di obj2. Anda tidak membuat salinan, hanya menunda rantai prototipe ketika anggota tidak ditemukan di obj2.
Nick Desaulniers

2
Ini TIDAK membuatnya "tanpa referensi", itu hanya memindahkan referensi ke prototipe. Itu masih referensi. Jika sebuah properti berubah dalam aslinya, maka properti prototipe di "clone". Itu sama sekali bukan tiruan.
Jimbo Jonny

13

Jawaban baru untuk pertanyaan lama! Jika Anda merasa senang menggunakan ECMAScript 2016 (ES6) dengan Spread Syntax , mudah.

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

Ini memberikan metode bersih untuk salinan objek yang dangkal. Membuat salinan yang dalam, artinya membuat salinan baru dari setiap nilai di setiap objek bersarang secara rekursif, membutuhkan solusi yang lebih berat di atas.

JavaScript terus berkembang.


2
itu tidak berfungsi ketika Anda memiliki fungsi yang didefinisikan pada objek
Petr Marek

sejauh yang saya lihat operator spread hanya bekerja dengan iterables - developer.mozilla.org mengatakan: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
Oleh

@ Oleh jadi gunakan `{... obj} alih-alih [... obj];`
manikant gautam
@manikantgautam Saya menggunakan Object.assign () sebelumnya, tapi sekarang memang sintaks penyebaran objek didukung di Chrome, Firefox (masih belum di Edge dan Safari) terbaru. Proposal ECMAScript-nya ... tapi Babel mendukungnya sejauh yang saya bisa lihat, jadi mungkin aman untuk digunakan.
Oleh
12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

Solusi ES6 jika Anda ingin (dangkal) mengkloning instance kelas dan bukan hanya objek properti.

flori
sumber
Bagaimana ini berbeda let cloned = Object.assign({}, obj)?
ceztko
10

Saya pikir ada jawaban yang sederhana dan berhasil. Dalam penyalinan yang dalam ada dua masalah:

  1. Jaga agar sifat independen satu sama lain.
  2. Dan menjaga metode tetap hidup pada objek yang dikloning.

Jadi saya pikir salah satu solusi sederhana adalah pertama serialisasi dan deserialize dan kemudian melakukan penugasan untuk menyalin fungsi juga.

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

Meskipun pertanyaan ini memiliki banyak jawaban, saya harap pertanyaan ini juga membantu.

MelakukanClever
sumber
Meskipun jika saya diizinkan untuk mengimpor lodash, saya lebih suka menggunakan lodash cloneDeep.
DilakukanClever
2
Saya menggunakan JSON.parse (JSON.stringify (sumber)). Selalu bekerja.
Misha
2
@Misha, dengan cara ini Anda akan kehilangan fungsinya. Istilah 'karya' memiliki banyak arti.
DilakukanClever
Dan perlu diingat bahwa, seperti yang saya sebutkan, hanya fungsi dari layer pertama yang akan disalin. Jadi, jika kita memiliki beberapa objek di dalam satu sama lain, maka satu-satunya cara adalah menyalin bidang demi bidang secara rekursif.
DilakukanClever
9

Untuk salinan dan klon yang dalam, JSON.stringify lalu JSON.parse objek:

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}}
Nishant Dwivedi
sumber
cukup pintar ... ada kerugian dari pendekatan ini?
Aleks
8

Ini adalah adaptasi dari kode A. Levy untuk juga menangani kloning fungsi dan banyak / referensi siklik - apa artinya ini adalah bahwa jika dua properti dalam pohon yang dikloning adalah referensi dari objek yang sama, pohon objek yang dikloning akan memiliki ini properti menunjuk ke satu dan klon yang sama dari objek yang direferensikan. Ini juga menyelesaikan kasus dependensi siklik yang, jika dibiarkan tidak tertangani, mengarah ke loop tak terbatas. Kompleksitas algoritma adalah O (n)

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

Beberapa tes cepat

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
Radu Simionescu
sumber
1
Pada September 2016, ini adalah satu - satunya solusi yang tepat untuk pertanyaan itu.
DomQ
6

Saya hanya ingin menambahkan semua Object.create solusi di posting ini, bahwa ini tidak berfungsi dengan cara yang diinginkan dengan nodejs.

Di Firefox hasil

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

adalah

{test:"test"}.

Dalam simpul itu

{}
heinob
sumber
Ini adalah warisan prototypal, bukan kloning.
13
1
@ d13 ketika argumen Anda valid, perhatikan bahwa tidak ada cara standar dalam JavaScript untuk mengkloning objek. Ini adalah warisan prototipikal, tetapi dapat digunakan sebagai klon namun jika Anda memahami konsep-konsepnya.
froginvasion
@froginvasion. Satu-satunya masalah dengan menggunakan Object.create adalah objek bersarang dan array hanyalah referensi penunjuk ke objek dan array bersarang prototipe. jsbin.com/EKivInO/2/edit?js,console . Secara teknis objek "kloning" harus memiliki properti uniknya sendiri yang tidak dibagikan referensi ke properti pada objek lain.
d13
@ d13 oke, saya mengerti maksud Anda sekarang. Tetapi yang saya maksudkan adalah bahwa terlalu banyak orang yang teralienasi dengan konsep pewarisan prototipikal, dan bagi saya gagal mempelajari cara kerjanya. Jika saya tidak salah, contoh Anda dapat diperbaiki dengan hanya menelepon Object.hasOwnPropertyuntuk memeriksa apakah Anda memiliki array atau tidak. Ya ini menambah kompleksitas tambahan untuk berurusan dengan warisan prototipikal.
froginvasion
6
function clone(src, deep) {

    var toString = Object.prototype.toString;
    if(!src && typeof src != "object"){
        //any non-object ( Boolean, String, Number ), null, undefined, NaN
        return src;
    }

    //Honor native/custom clone methods
    if(src.clone && toString.call(src.clone) == "[object Function]"){
        return src.clone(deep);
    }

    //DOM Elements
    if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
        return src.cloneNode(deep);
    }

    //Date
    if(toString.call(src) == "[object Date]"){
        return new Date(src.getTime());
    }

    //RegExp
    if(toString.call(src) == "[object RegExp]"){
        return new RegExp(src);
    }

    //Function
    if(toString.call(src) == "[object Function]"){
        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });

    }

    var ret, index;
    //Array
    if(toString.call(src) == "[object Array]"){
        //[].slice(0) would soft clone
        ret = src.slice();
        if(deep){
            index = ret.length;
            while(index--){
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }

    return ret;
};
pengguna1547016
sumber
2
if(!src && typeof src != "object"){. Saya pikir seharusnya ||tidak &&.
MikeM
6

Karena mindeavor menyatakan bahwa objek yang akan dikloning adalah objek yang 'dikonstruksi secara literal', solusinya mungkin dengan hanya menghasilkan objek beberapa kali daripada mengkloning turunan objek:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
Bert Regelink
sumber
6

Saya sudah menulis implementasi saya sendiri. Tidak yakin apakah itu dianggap sebagai solusi yang lebih baik:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

Berikut ini adalah implementasinya:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}
yazjisuhail
sumber
tidak bekerja untuk objek saya, meskipun kasus saya agak rumit.
Sajuuk