Pertanyaan Apa cara paling efisien untuk menduplikasi objek dalam JavaScript?


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

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

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


4538
2018-06-06 14:59


asal


Jawaban:


catatan: Ini adalah balasan untuk jawaban lain, bukan jawaban yang tepat untuk pertanyaan ini. Jika Anda ingin mengkloning objek cepat, silakan ikuti Saran Corban dalam jawaban mereka untuk pertanyaan ini.


Saya ingin mencatat bahwa .clone() metode dalam jQuery hanya klon elemen DOM. Untuk mengkloning objek JavaScript, Anda harus:

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

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

Informasi lebih lanjut dapat ditemukan di dokumentasi jQuery.

Saya juga ingin mencatat bahwa salinan yang mendalam sebenarnya jauh lebih pintar daripada apa yang ditunjukkan di atas - itu dapat menghindari banyak perangkap (mencoba untuk memperpanjang memperluas elemen DOM, misalnya). Ini sering digunakan dalam inti jQuery dan plugin untuk efek yang besar.


4062



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

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

JSON.parse(JSON.stringify(obj))

menjadi cara tercepat untuk mengkloning objek secara mendalam (itu mengalahkan jQuery.extend dengan bendera dalam ditetapkan benar oleh 10-20%).

jQuery.extend cukup cepat ketika bendera dalam disetel ke false (tiruan dangkal). Ini adalah pilihan yang baik, karena ini mencakup beberapa logika ekstra untuk validasi tipe dan tidak menyalin properti yang tidak terdefinisi, dll., Tetapi ini juga akan memperlambat Anda sedikit.

Jika Anda tahu struktur objek yang Anda coba kloning atau dapat menghindari array bertingkat dalam Anda dapat menulis sederhana for (var i in obj) loop untuk mengkloning objek Anda saat memeriksa hasOwnProperty dan itu akan jauh lebih cepat daripada jQuery.

Terakhir jika Anda mencoba mengkloning struktur objek yang dikenal dalam lingkaran panas, Anda bisa mendapatkan KINERJA LEBIH BANYAK dengan hanya melapisi prosedur kloning dan membangun objek secara manual.

Mesin penelusuran JavaScript menyedot pengoptimalan for..in loop dan memeriksa hasOwnProperty akan memperlambat Anda juga. Kloning manual saat kecepatan merupakan keharusan mutlak.

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

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

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


1868



Dengan asumsi bahwa Anda hanya memiliki variabel dan bukan fungsi apa pun di objek Anda, Anda cukup menggunakan:

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

402



Kloning Terstruktur

HTML5 mendefinisikan algoritma kloning internal "terstruktur" yang dapat menciptakan klon objek yang mendalam. Ini masih terbatas pada tipe bawaan tertentu, tetapi sebagai tambahan untuk beberapa tipe yang didukung oleh JSON itu juga mendukung Tanggal, RegExps, Peta, Set, Gumpalan, FileLists, ImageDatas, Array jarang, Array Diketik, dan mungkin lebih banyak di masa depan. Ini juga mempertahankan referensi dalam data kloning, memungkinkannya untuk mendukung struktur siklikal dan rekursif yang akan menyebabkan kesalahan untuk JSON.

Dukungan Langsung di Browser: Segera Hadir?

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

const clone = structuredClone(original);

Sampai ini dikirimkan, implementasi kloning terstruktur browser hanya diekspos secara tidak langsung.

Asynchronous Workaround: Usable.

Cara overhead-rendah untuk membuat kloning terstruktur dengan API yang ada adalah memposting data melalui satu port a MessageChannels. Port lainnya akan mengeluarkan a message acara dengan klon terstruktur yang dilampirkan .data. Sayangnya, mendengarkan peristiwa ini selalu tidak sinkron, 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();

Pekerjaan Sinkron: Mengerikan!

Tidak ada pilihan bagus untuk membuat klon terstruktur secara bersamaan. Berikut adalah beberapa hacks yang tidak praktis.

history.pushState() dan history.replaceState() keduanya membuat tiruan terstruktur dari argumen pertama mereka, dan berikan nilai itu ke history.state. Anda dapat menggunakan ini untuk membuat tiruan 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:

'use strict';

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

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

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

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

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

main();

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

Itu Notification konstruktor membuat tiruan terstruktur dari data yang terkait. Ini juga mencoba untuk menampilkan pemberitahuan browser kepada pengguna, tetapi ini akan diam-diam gagal 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:

'use strict';

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

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

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

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

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

main();


274



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


273



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

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

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

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

Baca lebih banyak...

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

132



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

91



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

81



Salinan mendalam berdasarkan kinerja: Peringkat dari yang terbaik hingga terburuk

  • Penugasan kembali "=" (string array, number array - hanya)
  • Slice (string array, number array - hanya)
  • Penggabungan (string array, number array - hanya)
  • Fungsi kustom: salinan for-loop atau rekursif
  • $ .extend jQuery
  • JSON.parse (string array, number array, object array - hanya)
  • Underscore.js_.clone (string array, number array - hanya)
  • Lo-Dash's _.cloneDeep

Salin dalam array string atau angka (satu tingkat - tidak ada referensi pointer):

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

Dimana penugasan ulang memiliki kinerja tercepat:

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

Dan .slice () memiliki performa 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

Salin dalam array objek (dua atau lebih tingkat - referensi pointer):

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

Tulis fungsi kustom (memiliki performa 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 performa yang lebih baik:


64