Apa cara paling efisien untuk mengkloning objek dalam JavaScript?

5180

Apa cara paling efisien untuk mengkloning objek JavaScript? Saya pernah melihat obj = eval(uneval(o));sedang digunakan, tetapi itu tidak standar dan hanya didukung oleh Firefox .

Saya sudah melakukan hal-hal seperti obj = JSON.parse(JSON.stringify(o));tetapi mempertanyakan efisiensinya.

Saya juga melihat fungsi penyalinan rekursif dengan berbagai kekurangan.
Saya terkejut tidak ada solusi kanonik.

jschrab
sumber
566
Eval tidak jahat. Menggunakan eval buruk adalah. Jika Anda takut efek sampingnya, Anda salah menggunakannya. Efek samping yang Anda takuti adalah alasan untuk menggunakannya. Apakah ada yang benar-benar menjawab pertanyaan Anda?
James
15
Objek kloning adalah bisnis yang rumit, terutama dengan objek kustom koleksi sewenang-wenang. Yang mungkin mengapa tidak ada cara out-of-the-box untuk melakukannya.
B01
12
eval()umumnya merupakan ide yang buruk karena banyak pengoptimal mesin Javascript harus dimatikan ketika berhadapan dengan variabel yang ditetapkan melaluieval . Hanya dengan memiliki eval()kode Anda dapat menyebabkan kinerja yang lebih buruk.
user56reinstatemonica8
2
Kemungkinan duplikat cara paling elegan untuk mengkloning objek JavaScript
John Slegers
12
Perhatikan bahwa JSONmetode ini akan kehilangan semua jenis Javascript yang tidak memiliki padanan dalam JSON. Misalnya: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))akan menghasilkan{a: null, b: null, c: null, g: false}
oriadam

Jawaban:

4731

Kloning dalam asli

Ini disebut "kloning terstruktur", bekerja secara eksperimental di Node 11 dan yang lebih baru, dan semoga akan mendarat di browser. Lihat jawaban ini untuk lebih jelasnya.

Kloning cepat dengan kehilangan data - JSON.parse / strify

Jika Anda tidak menggunakan Dates, fungsi, undefined, Infinity, regexps, Maps, Set, gumpalan, FileLists, ImageDatas, Array jarang, Diketik Array atau jenis kompleks lainnya dalam objek Anda, satu kapal yang sangat sederhana untuk klon dalam sebuah objek 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'
  re: /.*/,  // lost
}
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()

Lihat jawaban Corban untuk tolok ukur.

Kloning yang andal menggunakan perpustakaan

Karena objek kloning tidak sepele (tipe kompleks, referensi melingkar, fungsi dll), sebagian besar perpustakaan utama menyediakan fungsi untuk mengkloning objek. Jangan menemukan kembali roda - jika Anda sudah menggunakan perpustakaan, periksa apakah ia memiliki fungsi kloning objek. Sebagai contoh,

ES6

Untuk kelengkapan, perhatikan bahwa ES6 menawarkan dua mekanisme penyalinan dangkal: Object.assign()dan sintaksis penyebaran . yang menyalin nilai dari semua properti yang dapat dihitung sendiri dari satu objek ke objek lain. Sebagai contoh:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax
Dan Dascalescu
sumber
7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js di baris 276 (ada sedikit kode yang melakukan hal lain selain kode untuk "bagaimana melakukan ini di JS" ada :)
Rune FS
7
Berikut adalah kode JS di belakang jQuery deep copy, untuk siapa saja yang tertarik: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W
194
Wow! Hanya untuk menjadi sangat jelas: tidak tahu mengapa respons ini dipilih sebagai jawaban yang tepat, ini adalah jawaban untuk respons yang diberikan di bawah ini: stackoverflow.com/a/122190/6524 (yang merekomendasikan .clone(), yang bukan kode yang tepat untuk dijadikan gunakan dalam konteks ini). Sayangnya pertanyaan ini telah melalui begitu banyak revisi sehingga diskusi aslinya tidak lagi terlihat! Harap ikuti saran Corban dan tulis satu lingkaran atau salin properti langsung ke objek baru, jika Anda peduli dengan kecepatan. Atau coba sendiri!
John Resig
9
Ini adalah pertanyaan JavaScript (tidak menyebutkan jQuery).
gphilip
60
Bagaimana cara melakukan ini tanpa menggunakan jQuery?
Awesomeness01
2266

Lihat patokan ini: http://jsben.ch/#/bWfk9

Dalam tes saya sebelumnya di mana kecepatan adalah masalah utama yang saya temukan

JSON.parse(JSON.stringify(obj))

menjadi cara paling lambat untuk mengkloning suatu objek (itu lebih lambat dari jQuery.extend dengan deepflag true 10-20%).

jQuery.extend cukup cepat ketika deepflag diatur ke false(clone dangkal). Ini adalah pilihan yang baik, karena mencakup beberapa logika tambahan untuk validasi tipe dan tidak menyalin properti yang tidak ditentukan, dll., Tetapi ini juga akan sedikit memperlambat Anda.

Jika Anda mengetahui struktur objek yang Anda coba klon atau dapat menghindari array bersarang yang mendalam, Anda dapat menulis for (var i in obj)loop sederhana untuk mengkloning objek Anda saat memeriksa hasOwnProperty dan itu akan jauh lebih cepat daripada jQuery.

Terakhir jika Anda mencoba untuk mengkloning struktur objek yang dikenal dalam loop panas, Anda bisa mendapatkan KINERJA BANYAK BANYAK LAGI dengan hanya menyelaraskan prosedur klon dan secara manual membangun objek.

Mesin jejak JavaScript menyedot optimalisasi for..inloop dan memeriksa hasOwnProperty akan memperlambat Anda juga. Mengkloning manual ketika kecepatan adalah keharusan mutlak.

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

Hati-hati menggunakan JSON.parse(JSON.stringify(obj))metode pada Dateobjek - JSON.stringify(new Date())mengembalikan representasi string tanggal dalam format ISO, yang JSON.parse() tidak dikonversi kembali ke Dateobjek. Lihat jawaban ini untuk lebih jelasnya .

Selain itu, harap perhatikan bahwa, setidaknya di Chrome 65, kloning asli bukan cara yang tepat. Menurut JSPerf, melakukan kloning asli dengan membuat fungsi baru hampir 800x lebih lambat daripada menggunakan JSON.stringify yang sangat cepat sepanjang jalan di seluruh papan.

Perbarui untuk ES6

Jika Anda menggunakan Javascript ES6, coba metode asli ini untuk kloning atau salinan dangkal.

Object.assign({}, obj);
Corban Brook
sumber
4
@trysis Object.create tidak mengkloning objek, menggunakan objek prototipe ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser
105
Metode ini juga akan menghapus keysdari Anda object, yang memiliki functionsnilainya, karena JSONtidak mendukung fungsi.
Karlen Kishmiryan
39
Juga perlu diingat bahwa menggunakan JSON.parse(JSON.stringify(obj))Objek Tanggal juga akan mengubah tanggal kembali ke UTC dalam representasi string dalam format ISO8601 .
dnlgmzddr
31
Pendekatan JSON juga mencekik referensi melingkar.
kaya remer
28
@velop, Object.assign ({}, objToClone) sepertinya melakukan clone yang dangkal - menggunakannya saat bermain-main di konsol dev tools, objek clone masih menunjuk ke referensi objek yang dikloning. Jadi saya tidak berpikir itu benar-benar berlaku di sini.
Garrett Simpson
473

Dengan asumsi bahwa Anda hanya memiliki variabel dan tidak ada fungsi di objek Anda, Anda bisa menggunakan:

var newObject = JSON.parse(JSON.stringify(oldObject));
Sultan Shakir
sumber
86
kontra dari pendekatan ini seperti yang baru saja saya temukan adalah jika objek Anda memiliki fungsi apa pun (milik saya memiliki getter & setter internal) maka ini akan hilang ketika dirangkai .. Jika hanya itu yang Anda butuhkan, metode ini baik-baik saja ..
Markive
31
@Jason, Alasan mengapa metode ini lebih lambat daripada penyalinan dangkal (pada objek yang dalam) adalah bahwa metode ini, menurut definisi, salinan yang dalam. Tetapi karena JSONdiimplementasikan 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-objek / 79 ).
MiJyn
35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer
32
teknik ini akan menghancurkan juga semua Dateobjek yang disimpan di dalam objek, mengubahnya menjadi bentuk string.
fstab
13
Ini akan gagal untuk menyalin apa pun yang bukan bagian dari spesifikasi JSON ( json.org )
cdmckay
397

Kloning Terstruktur

Standar HTML mencakup algoritma kloning / serialisasi terstruktur internal yang dapat membuat klon objek dalam. Ini masih terbatas pada tipe bawaan tertentu, tetapi selain beberapa jenis yang didukung oleh JSON, ini juga mendukung Tanggal, RegExps, Peta, Set, Blob, FileLists, ImageDatas, Array yang jarang, Array yang Diketik, dan mungkin lebih banyak di masa depan . Itu juga mempertahankan referensi dalam data kloning, memungkinkannya untuk mendukung struktur siklus dan rekursif yang akan menyebabkan kesalahan untuk JSON.

Dukungan di Node.js: Eksperimental 🙂

The v8modul Node.js saat ini (per Node 11) memperlihatkan serialisasi API terstruktur secara langsung , tetapi fungsi ini masih ditandai sebagai "eksperimental", dan tunduk pada perubahan atau penghapusan dalam versi masa depan. Jika Anda menggunakan versi yang kompatibel, kloning objek semudah:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

Dukungan Langsung di Peramban: Mungkin Akhirnya? 😐

Browser saat ini tidak menyediakan antarmuka langsung untuk algoritma kloning terstruktur, tetapi structuredClone()fungsi global telah dibahas dalam whatwg / html # 793 di GitHub . Seperti yang diusulkan saat ini, menggunakannya untuk sebagian besar tujuan akan sesederhana:

const clone = structuredClone(original);

Kecuali jika ini dikirimkan, implementasi klon terstruktur browser hanya diekspos secara tidak langsung.

Pemecahan Masalah Asinkron: Dapat Digunakan. 😕

Cara overhead yang lebih rendah untuk membuat klon terstruktur dengan API yang ada adalah memposting data melalui satu port dari MessageChannels . Port lain akan memancarkan messageacara dengan klon terstruktur dari terlampir .data. Sayangnya, mendengarkan acara-acara ini selalu asinkron, dan alternatif sinkron kurang praktis.

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

Contoh Penggunaan:

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

Solusi Sinkron: Mengerikan! 🤢

Tidak ada opsi yang baik untuk membuat klon terstruktur secara serempak. Berikut adalah beberapa peretasan yang tidak praktis.

history.pushState()dan history.replaceState()keduanya membuat klon terstruktur dari argumen pertama mereka, dan menetapkan nilai itu history.state. Anda dapat menggunakan ini untuk membuat klon terstruktur dari objek apa pun seperti ini:

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

Contoh Penggunaan:

Meskipun sinkron, ini bisa sangat lambat. Itu menimbulkan semua overhead yang terkait dengan memanipulasi riwayat browser. Memanggil metode ini berulang kali dapat menyebabkan Chrome menjadi tidak responsif sementara.

The Notificationkonstruktor menciptakan klon terstruktur data yang terkait. Itu juga mencoba untuk menampilkan pemberitahuan browser kepada pengguna, tetapi ini akan gagal secara diam-diam kecuali Anda telah meminta izin pemberitahuan. Jika Anda memiliki izin untuk tujuan lain, kami akan segera menutup pemberitahuan yang kami buat.

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

Contoh Penggunaan:

Jeremy Banks
sumber
3
@rynah Saya hanya melihat melalui spek lagi dan Anda benar: history.pushState()dan history.replaceState()metode keduanya secara sinkron diatur history.stateke klon terstruktur dari argumen pertama mereka. Agak aneh, tapi berhasil. Saya memperbarui jawaban saya sekarang.
Jeremy Banks
40
Ini sangat salah! API itu tidak dimaksudkan untuk digunakan dengan cara ini.
Fardin K.
209
Sebagai orang yang menerapkan pushState di Firefox, saya merasakan campuran aneh kebanggaan dan rasa jijik pada retasan ini. Bagus, teman-teman.
Justin L.
pushState atau Notification hack tidak berfungsi untuk beberapa jenis objek seperti Function
Shishir Arora
323

Jika tidak ada yang builtin, Anda dapat mencoba:

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;
}
ConroyP
sumber
20
Solusi JQuery akan bekerja untuk elemen DOM tetapi tidak hanya Obyek apa pun. Mootool memiliki batas yang sama. Berharap mereka memiliki "klon" generik untuk sembarang objek ... Solusi rekursif harus bekerja untuk apa pun. Mungkin cara untuk pergi.
jschrab
5
Fungsi ini rusak jika objek yang dikloning memiliki konstruktor yang memerlukan parameter. Sepertinya kita dapat mengubahnya menjadi "var temp = new Object ()" dan membuatnya bekerja di setiap kasus, bukan?
Andrew Arnott
3
Andrew, jika Anda mengubahnya ke var temp = Object baru (), maka klon Anda tidak akan memiliki prototipe yang sama dengan objek asli. Coba gunakan: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
limscoder
1
Mirip dengan jawaban limscoder, lihat jawaban saya di bawah tentang cara melakukan ini tanpa memanggil konstruktor: stackoverflow.com/a/13333781/560114
Matt Browne
3
Untuk objek yang berisi referensi ke sub-bagian (yaitu, jaringan objek), ini tidak berfungsi: Jika dua referensi menunjuk ke sub-objek yang sama, salinannya berisi dua salinan berbeda. Dan jika ada referensi rekursif, fungsi tidak akan pernah berakhir (baik, setidaknya tidak seperti yang Anda inginkan :-) Untuk kasus-kasus umum ini, Anda harus menambahkan kamus objek yang sudah disalin, dan memeriksa apakah Anda sudah menyalinnya. ... Pemrogramannya rumit ketika Anda menggunakan bahasa yang sederhana
virtualnobi
153

Cara efisien untuk mengkloning (bukan deep-clone) suatu objek dalam satu baris kode

Sebuah Object.assign metode 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;
    }
  });
}
Eugene Tiurin
sumber
82
Ini tidak menyalin secara rekursif sehingga tidak benar-benar menawarkan solusi untuk masalah kloning objek.
mwhite
5
Metode ini berhasil, meskipun saya menguji beberapa dan _.extend ({}, (obj)) adalah yang TERCEPAT: 20x lebih cepat dari JSON.parse dan 60% lebih cepat dari Object.assign, misalnya. Ini menyalin semua sub-objek dengan cukup baik.
Nico
11
@white ada perbedaan antara clone dan deep-clone. Jawaban ini sebenarnya klon, tetapi tidak klon-dalam.
Meirion Hughes
57
op meminta klon dalam. ini tidak melakukan kloning yang mendalam.
user566245
9
Metode ini membuat salinan SHALLOW , dan bukan salinan DEEP ! Karena ini jawaban yang benar - benar salah !
Bharata
97

Kode:

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

Uji:

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);
Kamarey
sumber
3
bagaimana dengan var obj = {}danobj.a = obj
neaumusik
5
Saya tidak mengerti fungsi ini. Misalkan from.constructoradalah Datecontohnya. Bagaimana iftes ketiga akan tercapai ketika iftes kedua akan berhasil & menyebabkan fungsi kembali (sejak Date != Object && Date != Array)?
Adam McKee
1
@AdamMcKee Karena pengesahan argumen javascript dan penugasan variabel rumit . Pendekatan ini berfungsi dengan baik, termasuk tanggal (yang memang ditangani oleh tes kedua) - biola untuk menguji di sini: jsfiddle.net/zqv9q9c6 .
brichins
1
@NickSweeting: Coba - mungkin berhasil. Jika tidak - perbaiki dan perbarui jawabannya. Begitulah cara kerjanya di sini di komunitas :)
Kamarey
1
Fungsi ini tidak mengkloning regex dalam pengujian, kondisi "from.constructor! = Object && from.constructor! = Array" selalu mengembalikan true untuk konstruktor lain seperti Nomor, Tanggal, dan sebagainya.
aMarCruz
95

Inilah yang saya gunakan:

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;
}
Alan
sumber
8
Ini sepertinya tidak benar. cloneObject({ name: null })=>{"name":{}}
Niyaz
13
Ini disebabkan oleh hal bodoh lain dalam javascript typeof null > "object"tetapi Object.keys(null) > TypeError: Requested keys of a value that is not an object.ubah kondisinya menjadiif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us
Ini akan menugaskan properti enumerable inheren dari obj langsung ke klon, dan mengasumsikan bahwa obj adalah Obyek biasa.
RobG
Ini juga mengacaukan array, yang dapat dikonversi ke objek dengan tombol numerik.
blade
Tidak masalah jika Anda tidak menggunakan null.
Jorge Bucaran
78

Salinan mendalam menurut kinerja: Diurutkan dari yang terbaik hingga yang terburuk

  • Penugasan kembali "=" (array string, array angka - hanya)
  • Slice (array string, array angka - hanya)
  • Penggabungan (array string, array angka - hanya)
  • Fungsi kustom: untuk loop atau salinan rekursif
  • $ .extend jQuery
  • JSON.parse (array string, array angka, array objek - hanya)
  • Underscore.js 's _.clone (array string, angka array - saja)
  • Lo-Dash _.cloneDeep

Menyalin sederetan string atau angka (satu tingkat - tanpa petunjuk referensi):

Ketika array berisi angka dan string - fungsi seperti .slice (), .concat (), .splice (), operator penugasan "=", dan fungsi klon Underscore.js; akan membuat salinan yang dalam dari elemen-elemen array.

Di mana penugasan kembali memiliki kinerja tercepat:

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

Dan .slice () memiliki kinerja yang lebih baik daripada .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

Menyalin dalam-dalam array objek (dua level atau lebih - pointer referensi):

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

Tulis fungsi khusus (memiliki kinerja lebih cepat dari $ .extend () atau 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);

Gunakan fungsi utilitas pihak ketiga:

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

Di mana $ .extend jQuery memiliki kinerja yang lebih baik:

tfmontague
sumber
Saya menguji beberapa dan _.extend ({}, (obj)) adalah JAUH yang tercepat: 20x lebih cepat dari JSON.parse dan 60% lebih cepat dari Object.assign, misalnya. Ini menyalin semua sub-objek dengan cukup baik.
Nico
4
Semua contoh Anda dangkal, satu tingkat. Ini bukan jawaban yang bagus. Pertanyaannya adalah tentang kloning mendalam yaitu setidaknya dua tingkat.
Karl Morrison
1
Salinan yang dalam adalah ketika suatu objek disalin secara keseluruhan tanpa menggunakan pointer referensi ke objek lain. Teknik-teknik di bawah bagian "Salin dalam array objek", seperti jQuery.extend () dan fungsi kustom (yang bersifat rekursif) menyalin objek dengan "setidaknya dua level". Jadi, tidak semua contoh adalah salinan "satu level".
tfmontague
1
Saya suka fungsi salin khusus Anda, tetapi Anda harus mengecualikan nilai nol, jika tidak semua nilai nol dikonversi ke objek, yaitu:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi
2
@HossamMourad - Bug diperbaiki oleh Josi pada Feb1 (dalam komentar di atas), dan saya gagal memperbarui jawaban dengan benar. Maaf bug ini mengakibatkan refactor basis kode Anda.
tfmontague
64
var clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (var i in this) {
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        }
        else
        {
            newObj[i] = this[i];
        }
    }
    return newObj;
}; 

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});
Zibri
sumber
Jawaban yang bagus tetapi ini gagal untuk referensi melingkar.
Luke
59

Menyalin objek dalam JavaScript (saya pikir yang terbaik dan paling sederhana)

1. Menggunakan JSON.parse (JSON.stringify (objek));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2.Menggunakan metode yang dibuat

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

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Menggunakan Lo-Dash _.cloneDeep Link lodash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Menggunakan metode Object.assign ()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

TAPI SALAH SAAT

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Menggunakan tautan Underscore.js _.clone Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

TAPI SALAH SAAT

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

JSBEN.CH Play Benchmarking Performance 1 ~ 3 http://jsben.ch/KVQLd Kinerja Menyalin objek dalam JavaScript

Tính Ngô Quang
sumber
5
Object.assign()tidak melakukan salinan dalam
Roymunson
1
Anda harus menambahkan tolok ukur untuk ini; itu akan sangat membantu
jcollum
ketika saya menggunakan "metode yang dibuat" pada objek yang berisi array saya tidak dapat menggunakan pop () atau splice () di atasnya, saya tidak mengerti mengapa? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();itu melempar: TypeError: tmp.title.pop is not a function(tentu saja pop () berfungsi dengan baik jika saya hanya do let tmp = data; tetapi kemudian saya tidak dapat memodifikasi tmp tanpa mempengaruhi data)
hugogogo
Hei, contoh terakhir Anda salah. Menurut pendapat saya, Anda harus menggunakan _clone dan bukan _cloneDeep untuk contoh yang salah.
kenanyildiz
Metode yang dibuat ini (2.) tidak akan berfungsi untuk array, bukan?
Toivo Säwén
57

Ada perpustakaan (disebut "clone") , yang melakukan ini dengan cukup baik. Ini memberikan kloning / penyalinan rekursif paling lengkap dari objek arbitrer yang saya tahu. Ini juga mendukung referensi melingkar, yang belum tercakup oleh jawaban lain, belum.

Anda dapat menemukannya di npm , juga. Ini dapat digunakan untuk browser dan juga Node.js.

Berikut adalah contoh cara menggunakannya:

Instal dengan

npm install clone

atau bungkus dengan Ender .

ender build clone [...]

Anda juga dapat mengunduh kode sumber secara manual.

Kemudian Anda dapat menggunakannya dalam kode sumber Anda.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(Penafian: Saya penulis perpustakaan.)

pvorb
sumber
3
Klon npm telah sangat berharga bagi saya karena mengkloning objek bersarang secara sewenang-wenang. Ini adalah jawaban yang benar.
Andy Ray
apa kinerja lib Anda dibandingkan dengan katakanlah JSON.parse(JSON.stringify(obj))?
pkyeck
Berikut perpustakaan yang menyatakan bahwa ada opsi yang lebih cepat. Belum diuji.
pvorb
Solusi yang bagus dan ini mendukung referensi melingkar (tidak seperti parse JSON)
Luke
55

Cloning sebuah Object selalu menjadi perhatian di JS, tetapi itu semua tentang sebelum ES6, saya mendaftar berbagai cara menyalin objek dalam JavaScript di bawah ini, bayangkan Anda memiliki Object di bawah ini dan ingin memiliki salinan yang dalam tentang itu:

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

Ada beberapa cara untuk menyalin objek ini, tanpa mengubah asal:

1) ES5 +, Menggunakan fungsi sederhana untuk melakukan penyalinan untuk Anda:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj this object.");
}

2) ES5 +, menggunakan JSON.parse dan JSON.stringify.

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

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

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

5) Garis BawahJs & Loadash:

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

Semoga bantuan ini ...

Alireza
sumber
2
klon dalam garis bawah bukan klon dalam versi saat ini
Rogelio
Terima kasih. ya sebagai dokumen baru untuk Garis Bawah ... clone_.clone (objek) Buat klon dangkal yang disalin dari objek polos yang disediakan. Setiap objek atau array bersarang akan disalin dengan referensi, tidak digandakan. _.clone ({name: 'moe'}); => {name: 'moe'};
Alireza
59
Object.assigntidak menyalin dalam - dalam. Contoh: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Jika ini salinan yang dalam, y.a.bpasti masih c, tapi sekarang d.
kba
8
Object.assign () hanya mengkloning properti tingkat pertama!
haemse
5
apa fungsi cloneSO ()?
pastorello
53

Saya tahu ini adalah posting lama, tapi saya pikir ini mungkin bisa membantu orang berikutnya yang tersandung.

Selama Anda tidak menetapkan objek untuk apa pun itu tidak memiliki referensi dalam memori. Jadi untuk membuat objek yang ingin Anda bagikan di antara objek lain, Anda harus membuat pabrik seperti:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
Joe
sumber
16
Jawaban ini tidak benar-benar relevan karena pertanyaannya adalah: diberikan contoh b bagaimana cara membuat salinan c SAAT tidak tahu tentang pabrik atau tidak ingin menggunakan pabrik a. Alasan seseorang mungkin tidak ingin menggunakan pabrik adalah bahwa setelah instantiasi b mungkin diinisialisasi dengan data tambahan (misalnya input pengguna).
Noel Abrahams
12
Memang benar ini bukan jawaban untuk pertanyaan itu, tetapi saya pikir ini penting karena pertanyaan itu ada di sini karena itu adalah jawaban untuk pertanyaan yang saya curigai banyak orang yang datang ke sini benar-benar ingin bertanya.
Titik koma
8
Maaf kawan, saya tidak begitu mengerti mengapa begitu banyak upvotes. Mengkloning suatu objek adalah konsep yang cukup jelas, Anda mengerucutkan objek dari objek LAIN, dan itu tidak ada hubungannya dengan membuat yang baru dengan pola pabrik.
opensas
2
Meskipun ini berfungsi untuk objek yang telah ditentukan, "kloning" dengan cara ini tidak akan mengenali properti baru yang ditambahkan ke objek asli. Jika Anda membuat, tambahkan properti baru ke, lalu buat b. b tidak akan memiliki properti baru. Pada dasarnya pola pabrik tidak dapat diubah dengan properti baru. Ini bukan kloning yang paradigmatik. Lihat: jsfiddle.net/jzumbrun/42xejnbx
Jon
1
Saya pikir ini adalah saran yang bagus, secara umum, daripada menggunakan const defaultFoo = { a: { b: 123 } };Anda bisa pergi const defaultFoo = () => ({ a: { b: 123 } };dan masalah Anda terpecahkan. Namun, itu sebenarnya bukan jawaban untuk pertanyaan itu. Mungkin lebih masuk akal sebagai komentar pada pertanyaan, bukan jawaban lengkap.
Josh dari Qaribou
48

Jika Anda menggunakannya, perpustakaan Underscore.js memiliki metode klon .

var newObject = _.clone(oldObject);
itsadok
sumber
24
lodash memiliki metode cloneDeep, juga mendukung param lain untuk mengkloning untuk membuatnya lebih dalam: lodash.com/docs#clone dan lodash.com/docs#cloneDeep
opensas
12
@opensas setuju. Lodash umumnya lebih unggul daripada garis bawah
nha
7
Saya menganjurkan menghapus ini dan semua jawaban lain yang hanya merupakan referensi satu baris ke .clone(...)metode perpustakaan utilitas . Setiap perpustakaan utama akan memilikinya, dan jawaban singkat yang tidak terperinci yang diulang tidak berguna bagi kebanyakan pengunjung, yang tidak akan menggunakan perpustakaan itu.
Jeremy Banks
41

Berikut versi jawaban ConroyP di atas yang berfungsi bahkan jika konstruktor memiliki parameter yang diperlukan:

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

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

Fungsi ini juga tersedia di perpustakaan simpleoo saya .

Edit:

Ini versi yang lebih kuat (terima kasih kepada Justin McCandless, ini sekarang mendukung referensi siklik juga):

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

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

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

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

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

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

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

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

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

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

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

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

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

Berikut ini membuat dua instance dari objek yang sama. Saya menemukannya dan saya menggunakannya saat ini. Ini sederhana dan mudah digunakan.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
nathan rogers
sumber
Apakah ada yang salah dengan jawaban ini? Ini lebih berguna sebagai solusi mandiri, namun sederhana; tetapi solusi jQuery lebih populer. Mengapa demikian?
ceremcem
Ya tolong beri tahu saya. Tampaknya berfungsi sebagaimana mestinya, jika ada beberapa kerusakan tersembunyi di suatu tempat, saya perlu menggunakan solusi yang berbeda.
nathan rogers
4
Untuk objek sederhana, ini sekitar 6 kali lebih lambat di Chrome daripada jawaban yang diberikan, dan menjadi jauh lebih lambat saat kompleksitas objek bertambah. Ini berskala sangat dan dapat menghambat aplikasi Anda dengan sangat cepat.
tic
1
Anda tidak memerlukan data, hanya pemahaman tentang apa yang terjadi. Teknik kloning ini membuat serial seluruh objek menjadi string, lalu mem-parsing serialisasi string tersebut untuk membangun objek. Secara inheren itu hanya akan jauh lebih lambat daripada hanya mengatur ulang beberapa memori (yang merupakan apa yang dilakukan oleh klon yang lebih canggih). Tetapi dengan itu dikatakan, untuk proyek-proyek kecil hingga menengah (tergantung pada definisi Anda tentang "menengah") siapa yang peduli jika itu bahkan 1000x kurang efisien? Jika objek Anda kecil dan Anda tidak mengkloningnya 1000 ton hampir tidak ada yang masih hampir tidak ada.
machineghost
3
Juga, metode ini kehilangan metode (atau hal-hal apa pun yang tidak diperbolehkan di JSON), plus - JSON.stringify akan mengonversi objek Date menjadi string, ... dan bukan sebaliknya;) Menjauh dari solusi ini.
Tuan MT
22

Crockford menyarankan (dan saya lebih suka) menggunakan fungsi ini:

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

var newObject = object(oldObject);

Ini singkat, berfungsi seperti yang diharapkan dan Anda tidak perlu perpustakaan.


EDIT:

Ini untuk polyfill Object.create, jadi Anda juga bisa menggunakan ini.

var newObject = Object.create(oldObject);

CATATAN: Jika Anda menggunakan beberapa dari ini, Anda mungkin memiliki masalah dengan beberapa iterasi yang digunakan hasOwnProperty. Karena, createbuat objek kosong baru yang mewarisi oldObject. Tetapi masih bermanfaat dan praktis untuk objek kloning.

Contohnya jika oldObject.a = 5;

newObject.a; // is 5

tapi:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
protonfish
sumber
9
koreksi saya jika saya salah, tetapi bukankah itu fungsi beget Crockford untuk warisan prototypal? Bagaimana itu berlaku untuk mengkloning?
Alex Nolasco
3
Ya, saya takut dengan diskusi ini: Apa perbedaan praktis antara kloning, salinan, dan pewarisan prototypal, kapan sebaiknya Anda menggunakan masing-masing dan fungsi mana di halaman ini yang benar-benar melakukan apa? Saya menemukan halaman SO ini dengan googling "objek copy javascript". Apa yang sebenarnya saya cari adalah fungsi di atas, jadi saya kembali untuk berbagi. Dugaan saya adalah penanya juga sedang mencari ini.
Chris Broski
51
Perbedaan antara clone / copy dan inheritance adalah, bahwa - menggunakan contoh Anda, ketika saya mengubah properti oldObject, properti juga akan berubah di newObject. Jika Anda membuat salinan, Anda dapat melakukan apa yang Anda inginkan dengan oldObject tanpa mengubah newObject.
Ridcully
13
Ini akan merusak cek hasOwnProperty sehingga cara yang cukup rumit untuk mengkloning objek dan akan memberi Anda hasil yang tidak terduga.
Corban Brook
var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inheritance-patterns
Cody
22

Lodash memiliki metode _.cloneDeep (nilai) yang bagus:

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
opensas
sumber
5
Saya menganjurkan menghapus ini dan semua jawaban lain yang hanya merupakan referensi satu baris ke .clone(...)metode perpustakaan utilitas . Setiap perpustakaan utama akan memilikinya, dan jawaban singkat yang tidak terperinci yang diulang tidak berguna bagi kebanyakan pengunjung, yang tidak akan menggunakan perpustakaan itu.
Jeremy Banks
Cara yang lebih mudah adalah menggunakan _.merge({}, objA). Jika saja Lodash tidak bermutasi objek di tempat pertama maka clonefungsi tidak akan diperlukan.
Rebs
7
Google mencari objek kloning JS merujuk ke sini. Saya menggunakan Lodash jadi jawaban ini relevan bagi saya. Mari kita tidak pergi semua "wikipedia deletionist" pada jawaban.
Rebs
2
Dalam Node 9, JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects)) jauh lebih cepat daripada _.deepClone (arrayOfAbout5KFlatObjects).
Dan Dascalescu
21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
Mark Cidade
sumber
17
Masalah dengan metode, bahwa jika Anda memiliki sub objek dalam objek, referensi mereka akan dikloning, dan bukan nilai dari setiap sub objek.
Kamarey
1
hanya membuatnya rekursif sehingga sub objek akan dikloning dalam.
fiatjaf
hanya ingin tahu ... tidak akan klon variabel akan memiliki petunjuk ke properti dari objek asli? karena sepertinya tidak ada alokasi memori baru
Rupesh Patel
3
Iya. Ini hanya salinan dangkal, sehingga klon akan menunjuk ke objek yang sama persis menunjuk ke objek aslinya.
Mark Cidade
Ini bukan jawaban. Anda benar-benar hanya menjejali objek dengan referensi ke objek lain. Membuat perubahan pada objek sumber akan membuat perubahan pada "klon".
Shawn Whinnery
20

Salinan satu baris dangkal ( ECMAScript edisi ke-5 ):

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

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

Dan salin dangkal satu-liner ( ECMAScript edisi 6 , 2015):

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

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true
Mael Nison
sumber
6
Ini mungkin baik untuk objek sederhana, tetapi hanya menyalin nilai properti. Itu tidak menyentuh rantai prototipe dan dengan menggunakannya Object.keysmelompati properti yang tidak dapat dihitung dan diwariskan. Juga, itu kehilangan deskriptor properti dengan melakukan penugasan langsung.
Matt Bierner
Jika Anda menyalin prototipe juga, Anda akan kehilangan hanya non-enumerable dan deskriptor properti, ya? Cukup bagus. :)
sam
Selain kinerja, ini adalah cara yang sangat nyaman untuk menyalin objek secara dangkal. Saya sering menggunakan ini untuk memilah properti istirahat palsu dalam penugasan merusak dalam komponen Bereaksi saya.
mjohnsonengr
17

Hanya karena saya tidak melihat AngularJS disebutkan dan berpikir bahwa orang mungkin ingin tahu ...

angular.copy juga menyediakan metode menyalin objek dan array.

Dan Atkinson
sumber
atau bisa digunakan dengan cara yang sama seperti jQiery memperpanjang:angular.extend({},obj);
Galvani
2
@ Galvani: Perlu dicatat bahwa jQuery.extenddan angular.extendkeduanya adalah salinan dangkal. angular.copyadalah salinan yang dalam.
Dan Atkinson
16

Tampaknya belum ada operator deep clone yang ideal untuk objek mirip array. Seperti yang diilustrasikan oleh kode di bawah ini, cloner jQuery John Resig mengubah array dengan properti non-numerik menjadi objek yang bukan array, dan klon JSON RegDwight menjatuhkan properti non-numerik. Tes berikut menggambarkan poin-poin ini pada beberapa browser:

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

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

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

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)
Page Notes
sumber
14
seperti yang orang lain tunjukkan dalam komentar untuk jawaban Resig, jika Anda ingin mengkloning objek mirip array, Anda mengubah {} menjadi [] dalam panggilan perluasan, mis. jQuery.extend (true, [], obj)
Anentropic
15

Saya punya dua jawaban yang baik tergantung pada apakah tujuan Anda adalah untuk mengkloning "objek JavaScript lama biasa" atau tidak.

Mari kita juga berasumsi bahwa niat Anda adalah untuk membuat klon lengkap tanpa referensi prototipe kembali ke objek sumber. Jika Anda tidak tertarik pada klon lengkap, maka Anda dapat menggunakan banyak rutin Object.clone () yang disediakan di beberapa jawaban lain (pola Crockford).

Untuk objek JavaScript lama biasa, cara yang baik dan benar untuk mengkloning objek dalam runtimes modern cukup sederhana:

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

Perhatikan bahwa objek sumber harus objek JSON murni. Dengan kata lain, semua properti bersarangnya harus skalar (seperti boolean, string, array, objek, dll). Fungsi atau objek khusus seperti RegExp atau Date tidak akan dikloning.

Apakah efisien? Heck ya. Kami telah mencoba semua jenis metode kloning dan ini bekerja paling baik. Saya yakin beberapa ninja bisa menyulap metode yang lebih cepat. Tapi saya curiga kita berbicara tentang keuntungan marjinal.

Pendekatan ini sederhana dan mudah diimplementasikan. Bungkus menjadi fungsi kenyamanan dan jika Anda benar-benar perlu memeras keuntungan, lakukan di lain waktu.

Sekarang, untuk objek JavaScript yang tidak jelas, tidak ada jawaban yang sangat sederhana. Bahkan, tidak mungkin ada karena sifat dinamis fungsi JavaScript dan keadaan objek batin. Kloning mendalam struktur JSON dengan fungsi di dalamnya mengharuskan Anda membuat ulang fungsi-fungsi itu dan konteks dalamnya. Dan JavaScript sama sekali tidak memiliki cara standar untuk melakukan itu.

Cara yang benar untuk melakukan ini, sekali lagi, adalah melalui metode kenyamanan yang Anda nyatakan dan gunakan kembali dalam kode Anda. Metode kenyamanan dapat diberkahi dengan beberapa pemahaman tentang objek Anda sendiri sehingga Anda dapat memastikan untuk membuat kembali grafik dengan benar dalam objek baru.

Kami menulis sendiri, tetapi pendekatan umum terbaik yang pernah saya lihat dibahas di sini:

http://davidwalsh.name/javascript-clone

Ini ide yang tepat. Penulis (David Walsh) telah berkomentar kloning fungsi umum. Ini adalah sesuatu yang mungkin Anda pilih untuk dilakukan, tergantung pada kasus penggunaan Anda.

Gagasan utamanya adalah bahwa Anda perlu menangani instantiasi fungsi Anda secara khusus (atau kelas prototipal), berdasarkan tipe per. Di sini, dia memberikan beberapa contoh untuk RegExp dan Tanggal.

Tidak hanya kode ini singkat, tetapi juga sangat mudah dibaca. Cukup mudah untuk diperpanjang.

Apakah ini efisien? Heck ya. Mengingat tujuannya adalah untuk menghasilkan tiruan salinan-dalam yang benar, maka Anda harus memandu anggota-anggota dari grafik objek sumber. Dengan pendekatan ini, Anda dapat mengubah anggota anak mana yang harus dirawat dan cara menangani jenis kustom secara manual.

Jadi begitulah. Dua pendekatan. Keduanya efisien menurut saya.

Michael Uzquiano
sumber
13

Ini biasanya bukan solusi yang paling efisien, tetapi ia melakukan apa yang saya butuhkan. Kotak uji sederhana di bawah ...

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

    clones = clones || [];

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

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

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

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

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

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

Tes susunan siklik ...

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

Tes fungsi ...

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

AngularJS

Nah, jika Anda menggunakan sudut Anda bisa melakukan ini juga

var newObject = angular.copy(oldObject);
azerafati
sumber
11

Saya tidak setuju dengan jawaban dengan suara terbesar di sini . Sebuah Rekursif Jauh Clone adalah jauh lebih cepat daripada JSON.parse (JSON.stringify (obj)) pendekatan disebutkan.

Dan inilah fungsi untuk referensi cepat:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}
prograhammer
sumber
2
Saya menyukai pendekatan ini tetapi tidak menangani tanggal dengan benar; pertimbangkan untuk menambahkan sesuatu seperti if(o instanceof Date) return new Date(o.valueOf());setelah memeriksa null`
Luis
Gangguan pada referensi melingkar.
Harry
Di Firefox stabil terbaru, ini jauh lebih lama daripada strategi lain di tautan Jsben.ch itu, dengan urutan besarnya atau lebih. Itu mengalahkan yang lain di arah yang salah.
WBT
11
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};
Dima
sumber
10

Hanya ketika Anda bisa menggunakan ECMAScript 6 atau transpiler .

Fitur:

  • Tidak akan memicu pengambil / penyetel saat menyalin.
  • Mempertahankan pengambil / penyetel.
  • Mempertahankan informasi prototipe.
  • Bekerja dengan gaya penulisan OO objek-literal dan fungsional .

Kode:

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;
}
andrew
sumber
9

Berikut adalah metode clone () komprehensif yang dapat mengkloning objek JavaScript apa pun. Ini menangani hampir semua kasus:

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
Ini mengubah primitif menjadi objek pembungkus, bukan solusi yang baik dalam banyak kasus.
Danubian Sailor
@DanubianSailor - Saya rasa tidak ... sepertinya mengembalikan primitif segera dari awal, dan sepertinya tidak melakukan apa-apa kepada mereka yang akan mengubahnya menjadi objek pembungkus saat dikembalikan.
Jimbo Jonny