Pertanyaan Bagaimana cara saya mengkloning objek JavaScript dengan benar?


Saya punya objek, x. Saya ingin menyalinnya sebagai objek y, seperti yang berubah y jangan modifikasi 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 harfiah.

Bagaimana cara saya mengkloning objek JavaScript dengan benar?


2395


asal


Jawaban:


Jawaban yang diperbarui

Cukup gunakan Object.assign () seperti yang disarankan sini

Tetapi ketahuilah bahwa ini hanya membuat salinan yang dangkal. Objek bersarang masih disalin sebagai referensi.


Jawaban yang kedaluwarsa

Untuk melakukan ini untuk objek apa pun di JavaScript tidak akan sederhana atau lugas. Anda akan mengalami masalah salah mengambil atribut dari prototipe objek yang harus ditinggalkan dalam prototipe dan tidak disalin ke instance baru. Jika, misalnya, Anda menambahkan a clone metode untuk Object.prototypekarena beberapa jawaban menggambarkan, Anda harus secara eksplisit mengabaikan atribut itu. Tetapi bagaimana jika ada metode tambahan lainnya ditambahkan Object.prototype, atau prototipe antara lainnya, yang tidak Anda ketahui? Dalam hal ini, Anda akan menyalin atribut yang tidak seharusnya Anda miliki, jadi Anda perlu mendeteksi atribut non-lokal yang tidak terduga dengan hasOwnProperty metode.

Selain atribut non-enumerable, Anda akan menghadapi masalah yang lebih sulit saat Anda mencoba menyalin objek yang memiliki properti tersembunyi. Sebagai contoh, prototype adalah properti tersembunyi dari suatu fungsi. Juga, prototipe objek dirujuk dengan atribut __proto__, yang juga tersembunyi, dan tidak akan disalin oleh untuk / di iterasi berulang atas atribut objek sumber. kupikir __proto__ mungkin khusus untuk interpreter JavaScript Firefox dan mungkin ada 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 menemukannya secara otomatis.

Namun halangan lain dalam pencarian solusi elegan adalah masalah pengaturan warisan prototipe dengan benar. Jika prototipe objek sumber Anda Object, lalu cukup buat objek umum baru dengan {} akan bekerja, tetapi jika prototipe sumbernya adalah beberapa keturunan Object, maka Anda akan kehilangan anggota tambahan dari prototipe yang Anda lewatkan menggunakan hasOwnProperty filter, atau yang berada di prototipe, tetapi tidak enumerable di tempat pertama. Satu solusi mungkin untuk memanggil objek sumber constructor properti untuk mendapatkan objek salinan awal dan kemudian salin atribut, tetapi kemudian Anda masih tidak akan mendapatkan atribut non-enumerable. Misalnya, a Date objek 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 d1 akan menjadi 5 detik di belakang itu d2. Cara untuk membuatnya Date sama dengan yang lain adalah dengan memanggil setTime metode, tapi itu khusus untuk Date kelas. Saya tidak berpikir ada solusi umum anti peluru untuk masalah ini, meskipun saya akan senang salah!

Ketika saya harus mengimplementasikan penyalinan mendalam secara umum, saya akhirnya berkompromi dengan mengasumsikan bahwa saya hanya perlu menyalin sebuah dataran Object, Array, Date, String, Number, atau Boolean. 3 tipe terakhir tidak dapat diubah, jadi saya bisa melakukan copy dangkal dan tidak khawatir tentang perubahannya. Saya lebih jauh berasumsi bahwa setiap elemen terkandung di dalamnya Object atau Array juga akan menjadi salah satu dari 6 tipe sederhana dalam daftar itu. Ini dapat diselesaikan 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 dalam 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 mampu menangani objek JavaScript, tetapi mungkin cukup untuk banyak tujuan selama Anda tidak menganggap bahwa itu hanya akan bekerja untuk apa pun yang Anda lempar padanya.


1308



Dengan jQuery, Anda bisa salinan dangkal dengan memperpanjang:

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

perubahan berikutnya pada copyObject tidak akan mempengaruhi originalObject, dan sebaliknya.

Atau membuat salinan dalam:

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

712



Jika Anda tidak menggunakan fungsi dalam objek Anda, satu liner yang sangat sederhana bisa menjadi berikut:

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

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

Lihat juga artikel ini tentang algoritma kloning terstruktur dari browser yang digunakan saat memposting pesan ke dan dari seorang pekerja. Ini juga mengandung fungsi untuk kloning mendalam.


687



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

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

Namun perlu disadari bahwa objek bersarang masih disalin sebagai referensi.


508



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

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

  1. Di mana warisan seperti itu berguna (duh!)
  2. Di mana objek sumber tidak akan dimodifikasi, sehingga membuat hubungan antara 2 objek menjadi tidak 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 yang lebih lama membutuhkan polyfill.


113



Cara elegan untuk mengkloning objek Javascript 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;
    }
  });
}

105



Per MDN:

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

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


103



Jika Anda baik-baik saja dengan salinan dangkal, perpustakaan underscore.js memiliki klon metode.

y = _.clone(x);

atau Anda dapat memperpanjangnya seperti

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

70



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

situasi awal

aku ingin dalam-copy sebuah Javascript Object dengan semua anak-anaknya dan anak-anak mereka dan seterusnya. Tapi karena saya bukan pengembang biasa, saya Object punya normal  properties, circular structures dan bahkan nested objects.

Jadi ayo buat a circular structure dan a nested object pertama.

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

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

Mari kita bawa semuanya bersama Object bernama a.

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

Selanjutnya, kami ingin menyalin a menjadi variabel bernama b dan 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 cari solusi.

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, 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 rekursif dari objek dan menangani jenis lain juga, seperti Date, tapi itu bukan keharusan.

var b = cloneSO(a);

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

Rekursi dan circular structures tidak 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 yang sederhana larutan setelah beberapa googling. Ini 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 bekerja dengan struktur bersarang di dalamnya.

polyfill untuk solusi asli

Ada polyfill untuk Object.create di browser lama seperti IE 8. Ini seperti 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 F di luar ruang lingkup sehingga kita dapat melihat apa instanceof memberitahu 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, tapi output sedikit lebih buruk.

solusi yang lebih baik (tetapi tidak sempurna)

Saat menggali, saya menemukan pertanyaan serupa (Dalam Javascript, ketika melakukan salinan yang mendalam, bagaimana cara menghindari siklus, karena properti menjadi "ini"?) untuk yang satu ini, tetapi dengan solusi yang 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 hasilnya ...

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 instance dari nested dan circ untuk Object.

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

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

kesimpulan

Solusi terakhir menggunakan rekursi dan cache, mungkin bukan yang terbaik, tapi itu a nyata salinan mendalam dari objek. Ini menangani sederhana properties, circular structures dan nested object, tapi itu akan mengacaukan contoh mereka saat kloning.

http://jsfiddle.net/einfallstoll/N4mr2/


63