Salin dalam di ES6 menggunakan sintaks penyebaran


99

Saya mencoba membuat metode peta salinan dalam untuk proyek Redux saya yang akan bekerja dengan objek daripada array. Saya membaca bahwa di Redux setiap negara bagian tidak boleh mengubah apa pun di negara bagian sebelumnya.

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    output[key] = callback.call(this, {...object[key]});

    return output;
    }, {});
}

Berhasil:

    return mapCopy(state, e => {

            if (e.id === action.id) {
                 e.title = 'new item';
            }

            return e;
        })

Namun itu tidak menyalin item dalam jadi saya perlu mengubahnya menjadi:

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    let newObject = {...object[key]};
    newObject.style = {...newObject.style};
    newObject.data = {...newObject.data};

    output[key] = callback.call(this, newObject);

    return output;
    }, {});
}

Ini kurang elegan karena perlu mengetahui objek mana yang dilewati. Apakah ada cara di ES6 untuk menggunakan sintaks penyebaran untuk menyalin objek secara mendalam?



8
Ini adalah masalah XY. Anda tidak perlu banyak bekerja pada properti dalam di redux. sebagai gantinya Anda harus membuat peredam lain yang bekerja pada potongan anak dari bentuk status dan kemudian digunakan combineReducersuntuk menyusun keduanya (atau lebih) bersama-sama. Jika Anda menggunakan teknik redux idiomatik, masalah objek kloning mendalam Anda akan hilang.
Terima kasih

Jawaban:


72

Tidak ada fungsi seperti itu yang ada di dalam ES6. Saya rasa Anda memiliki beberapa opsi tergantung pada apa yang ingin Anda lakukan.

Jika Anda benar-benar ingin menyalin lebih dalam:

  1. Gunakan perpustakaan. Misalnya, lodash memiliki cloneDeepmetode.
  2. Terapkan fungsi kloning Anda sendiri.

Solusi Alternatif Untuk Masalah Khusus Anda (Tanpa Salinan Dalam)

Namun, menurut saya, jika Anda bersedia mengubah beberapa hal, Anda dapat menghemat beberapa pekerjaan. Saya berasumsi bahwa Anda mengontrol semua situs panggilan ke fungsi Anda.

  1. Tentukan bahwa semua callback yang diteruskan mapCopyharus mengembalikan objek baru alih-alih memutasi objek yang sudah ada. Sebagai contoh:

    mapCopy(state, e => {
      if (e.id === action.id) {
        return Object.assign({}, e, {
          title: 'new item'
        });
      } else {  
        return e;
      }
    });

    Ini menggunakan Object.assignuntuk membuat objek baru, menyetel properti epada objek baru itu, lalu menyetel judul baru pada objek baru itu. Ini berarti Anda tidak pernah mengubah objek yang ada dan hanya membuat yang baru jika diperlukan.

  2. mapCopy bisa sangat sederhana sekarang:

    export const mapCopy = (object, callback) => {
      return Object.keys(object).reduce(function (output, key) {
        output[key] = callback.call(this, object[key]);
        return output;
      }, {});
    }

Intinya, mapCopymempercayai pemanggilnya untuk melakukan hal yang benar. Inilah sebabnya saya mengatakan ini mengasumsikan Anda mengontrol semua situs panggilan.


3
Object.assign tidak menyalin objek secara mendalam. lihat developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Object.assign () menyalin nilai properti. "Jika nilai sumber adalah referensi ke objek, itu hanya menyalin nilai referensi itu."
Greg Somers

Baik. Ini adalah solusi alternatif yang tidak melibatkan penyalinan mendalam. Saya akan memperbarui jawaban saya menjadi lebih eksplisit tentang itu.
Frank Tan

104

Sebagai gantinya gunakan ini untuk salinan dalam

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

var oldObject = {
  name: 'A',
  address: {
    street: 'Station Road',
    city: 'Pune'
  }
}
var newObject = JSON.parse(JSON.stringify(oldObject));

newObject.address.city = 'Delhi';
console.log('newObject');
console.log(newObject);
console.log('oldObject');
console.log(oldObject);


64
Ini hanya berfungsi jika Anda tidak perlu menggandakan fungsi. JSON akan mengabaikan semua fungsi sehingga Anda tidak akan memilikinya di klon.
Noland

7
Selain dari fungsi, Anda akan memiliki masalah dengan undefined dan null menggunakan metode ini
James Heazlewood

2
Anda juga akan mengalami masalah dengan kelas yang ditentukan pengguna, karena rantai prototipe tidak berseri.
Patrick Roberts

8
Solusi Anda menggunakan serialisasi JSON memiliki beberapa masalah. Dengan melakukan ini, Anda akan kehilangan properti Javascript yang tidak memiliki tipe setara di JSON, seperti Function atau Infinity. Properti apa pun yang ditetapkan ke tidak ditentukan akan diabaikan oleh JSON.stringify, menyebabkannya terlewatkan pada objek yang digandakan. Selain itu, beberapa objek diubah menjadi string, seperti Tanggal, Set, Peta, dan banyak lainnya.
Jonathan Brizio

2
Saya mengalami mimpi buruk yang mengerikan saat mencoba membuat salinan sebenarnya dari sebuah array objek - objek yang pada dasarnya adalah nilai data, tanpa fungsi. Jika hanya itu yang perlu Anda khawatirkan, maka pendekatan ini bekerja dengan baik.
Charlie

29

Dari MDN

Catatan: Sintaks penyebaran secara efektif berjalan satu tingkat saat menyalin larik. Oleh karena itu, mungkin tidak cocok untuk menyalin larik multidimensi seperti yang ditunjukkan contoh berikut (sama dengan Object.assign () dan sintaks sebar).

Secara pribadi, saya sarankan menggunakan fungsi cloneDeep Lodash untuk kloning objek / array multi-level.

Berikut adalah contoh yang berfungsi:

const arr1 = [{ 'a': 1 }];

const arr2 = [...arr1];

const arr3 = _.clone(arr1);

const arr4 = arr1.slice();

const arr5 = _.cloneDeep(arr1);

const arr6 = [...{...arr1}]; // a bit ugly syntax but it is working!


// first level
console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // false
console.log(arr1 === arr4); // false
console.log(arr1 === arr5); // false
console.log(arr1 === arr6); // false

// second level
console.log(arr1[0] === arr2[0]); // true
console.log(arr1[0] === arr3[0]); // true
console.log(arr1[0] === arr4[0]); // true
console.log(arr1[0] === arr5[0]); // false
console.log(arr1[0] === arr6[0]); // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>


4
arr6 tidak bekerja untuk saya. Di browser (chrome 59.0 yang mendukung ES6 saya mendapatkan Uncaught SyntaxError: Unexpected token ... dan di node 8.9.3 yang mendukung ES7 saya mendapatkan TypeError: undefined bukan functionat repl: 1: 22
Achi Even-dar

@ AchiEven-dar bukan tuan kenapa kamu mendapat error. Anda dapat menjalankan kode ini langsung di stackoverflow dengan menekan tombol biru Run code snippetdan seharusnya berjalan dengan benar.
Mina Luke

3
arr6 juga tidak bekerja untuk saya. Di browser - chrome 65
yehonatan yehezkel

18

Saya sering menggunakan ini:

function deepCopy(obj) {
    if(typeof obj !== 'object' || obj === null) {
        return obj;
    }

    if(obj instanceof Date) {
        return new Date(obj.getTime());
    }

    if(obj instanceof Array) {
        return obj.reduce((arr, item, i) => {
            arr[i] = deepCopy(item);
            return arr;
        }, []);
    }

    if(obj instanceof Object) {
        return Object.keys(obj).reduce((newObj, key) => {
            newObj[key] = deepCopy(obj[key]);
            return newObj;
        }, {})
    }
}

3
const a = {
  foods: {
    dinner: 'Pasta'
  }
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

Menggunakan JSON.stringifydan JSON.parsemerupakan cara terbaik. Karena dengan menggunakan operator spread kita tidak akan mendapatkan jawaban yang efisien jika objek json berisi objek lain di dalamnya. kita perlu menentukannya secara manual.


1
function deepclone(obj) {
    let newObj = {};

    if (typeof obj === 'object') {
        for (let key in obj) {
            let property = obj[key],
                type = typeof property;
            switch (type) {
                case 'object':
                    if( Object.prototype.toString.call( property ) === '[object Array]' ) {
                        newObj[key] = [];
                        for (let item of property) {
                            newObj[key].push(this.deepclone(item))
                        }
                    } else {
                        newObj[key] = deepclone(property);
                    }
                    break;
                default:
                    newObj[key] = property;
                    break;

            }
        }
        return newObj
    } else {
        return obj;
    }
}

1
// use: clone( <thing to copy> ) returns <new copy>
// untested use at own risk
function clone(o, m){
  // return non object values
  if('object' !==typeof o) return o
  // m: a map of old refs to new object refs to stop recursion
  if('object' !==typeof m || null ===m) m =new WeakMap()
  var n =m.get(o)
  if('undefined' !==typeof n) return n
  // shallow/leaf clone object
  var c =Object.getPrototypeOf(o).constructor
  // TODO: specialize copies for expected built in types i.e. Date etc
  switch(c) {
    // shouldn't be copied, keep reference
    case Boolean:
    case Error:
    case Function:
    case Number:
    case Promise:
    case String:
    case Symbol:
    case WeakMap:
    case WeakSet:
      n =o
      break;
    // array like/collection objects
    case Array:
      m.set(o, n =o.slice(0))
      // recursive copy for child objects
      n.forEach(function(v,i){
        if('object' ===typeof v) n[i] =clone(v, m)
      });
      break;
    case ArrayBuffer:
      m.set(o, n =o.slice(0))
      break;
    case DataView:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.byteLength))
      break;
    case Map:
    case Set:
      m.set(o, n =new (c)(clone(Array.from(o.entries()), m)))
      break;
    case Int8Array:
    case Uint8Array:
    case Uint8ClampedArray:
    case Int16Array:
    case Uint16Array:
    case Int32Array:
    case Uint32Array:
    case Float32Array:
    case Float64Array:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.length))
      break;
    // use built in copy constructor
    case Date:
    case RegExp:
      m.set(o, n =new (c)(o))
      break;
    // fallback generic object copy
    default:
      m.set(o, n =Object.assign(new (c)(), o))
      // recursive copy for child objects
      for(c in n) if('object' ===typeof n[c]) n[c] =clone(n[c], m)
  }
  return n
}

Komentar ada di dalam kode untuk mereka yang mencari penjelasan.
Wookies-Will-Code

1
const cloneData = (dataArray) => {
    newData= []
    dataArray.forEach((value) => {
        newData.push({...value})
    })
    return newData
}
  • a = [{nama: "siva"}, {nama: "siva1"}];
  • b = myCopy (a)
  • b === a // false`

1

Saya sendiri mendapatkan jawaban ini hari lalu, mencoba menemukan cara untuk menyalin struktur kompleks secara mendalam, yang mungkin termasuk tautan rekursif. Karena saya tidak puas dengan apa pun yang disarankan sebelumnya, saya menerapkan roda ini sendiri. Dan itu bekerja dengan cukup baik. Semoga bisa membantu seseorang.

Contoh penggunaan:

OriginalStruct.deep_copy = deep_copy; // attach the function as a method

TheClone = OriginalStruct.deep_copy();

Silakan lihat di https://github.com/latitov/JS_DeepCopy untuk contoh langsung bagaimana menggunakannya, dan juga deep_print () ada di sana.

Jika Anda membutuhkannya dengan cepat, inilah sumber fungsi deep_copy ():

function deep_copy() {
    'use strict';   // required for undef test of 'this' below

    // Copyright (c) 2019, Leonid Titov, Mentions Highly Appreciated.

    var id_cnt = 1;
    var all_old_objects = {};
    var all_new_objects = {};
    var root_obj = this;

    if (root_obj === undefined) {
        console.log(`deep_copy() error: wrong call context`);
        return;
    }

    var new_obj = copy_obj(root_obj);

    for (var id in all_old_objects) {
        delete all_old_objects[id].__temp_id;
    }

    return new_obj;
    //

    function copy_obj(o) {
        var new_obj = {};
        if (o.__temp_id === undefined) {
            o.__temp_id = id_cnt;
            all_old_objects[id_cnt] = o;
            all_new_objects[id_cnt] = new_obj;
            id_cnt ++;

            for (var prop in o) {
                if (o[prop] instanceof Array) {
                    new_obj[prop] = copy_array(o[prop]);
                }
                else if (o[prop] instanceof Object) {
                    new_obj[prop] = copy_obj(o[prop]);
                }
                else if (prop === '__temp_id') {
                    continue;
                }
                else {
                    new_obj[prop] = o[prop];
                }
            }
        }
        else {
            new_obj = all_new_objects[o.__temp_id];
        }
        return new_obj;
    }
    function copy_array(a) {
        var new_array = [];
        if (a.__temp_id === undefined) {
            a.__temp_id = id_cnt;
            all_old_objects[id_cnt] = a;
            all_new_objects[id_cnt] = new_array;
            id_cnt ++;

            a.forEach((v,i) => {
                if (v instanceof Array) {
                    new_array[i] = copy_array(v);
                }
                else if (v instanceof Object) {
                    new_array[i] = copy_object(v);
                }
                else {
                    new_array[i] = v;
                }
            });
        }
        else {
            new_array = all_new_objects[a.__temp_id];
        }
        return new_array;
    }
}

Bersulang@!


1

Inilah algoritma deep copy saya.

const DeepClone = (obj) => {
     if(obj===null||typeof(obj)!=='object')return null;
    let newObj = { ...obj };

    for (let prop in obj) {
      if (
        typeof obj[prop] === "object" ||
        typeof obj[prop] === "function"
      ) {
        newObj[prop] = DeepClone(obj[prop]);
      }
    }

    return newObj;
  };

Anda juga perlu memeriksa apakah 'obj [prop]! == null' karena typeof (null) juga mengembalikan 'object'
Pramod Mali

0

Berikut adalah fungsi deepClone yang menangani semua tipe data primitif, larik, objek, fungsi

function deepClone(obj){
	if(Array.isArray(obj)){
		var arr = [];
		for (var i = 0; i < obj.length; i++) {
			arr[i] = deepClone(obj[i]);
		}
		return arr;
	}

	if(typeof(obj) == "object"){
		var cloned = {};
		for(let key in obj){
			cloned[key] = deepClone(obj[key])
		}
		return cloned;	
	}
	return obj;
}

console.log( deepClone(1) )

console.log( deepClone('abc') )

console.log( deepClone([1,2]) )

console.log( deepClone({a: 'abc', b: 'def'}) )

console.log( deepClone({
  a: 'a',
  num: 123,
  func: function(){'hello'},
  arr: [[1,2,3,[4,5]], 'def'],
  obj: {
    one: {
      two: {
        three: 3
      }
    }
  }
}) ) 

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.