คัดลอกลึกใน ES6 โดยใช้ไวยากรณ์การแพร่กระจาย


106

ฉันกำลังพยายามสร้างวิธีการคัดลอกแผนที่แบบเจาะลึกสำหรับโปรเจ็กต์ Redux ของฉันซึ่งจะทำงานกับอ็อบเจ็กต์แทนที่จะเป็นอาร์เรย์ ฉันอ่านว่าใน Redux แต่ละรัฐไม่ควรเปลี่ยนแปลงอะไรในสถานะก่อนหน้านี้

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

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

    return output;
    }, {});
}

มันได้ผล:

    return mapCopy(state, e => {

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

            return e;
        })

อย่างไรก็ตามมันไม่ได้คัดลอกรายการภายในอย่างลึกล้ำดังนั้นฉันต้องปรับแต่งเพื่อ:

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

สิ่งนี้มีความหรูหราน้อยกว่าเนื่องจากต้องการทราบว่าวัตถุใดถูกส่งผ่าน มีวิธีใน ES6 ในการใช้ไวยากรณ์การแพร่กระจายเพื่อคัดลอกวัตถุในระดับลึกหรือไม่?


ดูstackoverflow.com/questions/27936772/...

8
นี่คือปัญหา XY คุณไม่ควรทำงานมากกับคุณสมบัติระดับลึกใน redux แต่คุณควรสร้างตัวลดอื่นที่ทำงานกับชิ้นส่วนลูกของรูปร่างสถานะจากนั้นใช้combineReducersเพื่อประกอบทั้งสอง (หรือมากกว่า) เข้าด้วยกัน หากคุณใช้เทคนิคการซ้ำซ้อนของสำนวนปัญหาของการโคลนวัตถุลึก ๆ ของคุณจะหมดไป
ขอบคุณ

คำตอบ:


73

ไม่มีฟังก์ชันดังกล่าวในตัว ES6 ฉันคิดว่าคุณมีสองทางเลือกขึ้นอยู่กับสิ่งที่คุณต้องการทำ

หากคุณต้องการคัดลอกอย่างละเอียด:

  1. ใช้ไลบรารี ตัวอย่างเช่น lodash มีcloneDeepวิธีการ
  2. ใช้ฟังก์ชันการโคลนของคุณเอง

ทางเลือกในการแก้ปัญหาเฉพาะของคุณ (ไม่มี Deep Copy)

อย่างไรก็ตามฉันคิดว่าถ้าคุณเต็มใจที่จะเปลี่ยนแปลงสองสามอย่างคุณสามารถช่วยตัวเองได้บ้าง ฉันสมมติว่าคุณควบคุมไซต์การโทรทั้งหมดตามหน้าที่ของคุณ

  1. ระบุว่าการเรียกกลับทั้งหมดที่ส่งผ่านไปmapCopyต้องส่งคืนอ็อบเจ็กต์ใหม่แทนที่จะกลายพันธุ์อ็อบเจ็กต์ที่มีอยู่ ตัวอย่างเช่น:

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

    สิ่งนี้ใช้Object.assignเพื่อสร้างวัตถุใหม่กำหนดคุณสมบัติของeวัตถุใหม่นั้นจากนั้นตั้งชื่อเรื่องใหม่ให้กับวัตถุใหม่นั้น ซึ่งหมายความว่าคุณจะไม่กลายพันธุ์วัตถุที่มีอยู่และสร้างวัตถุใหม่เมื่อจำเป็นเท่านั้น

  2. mapCopy สามารถทำได้ง่ายมากตอนนี้:

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

โดยพื้นฐานแล้วmapCopyคือการไว้วางใจให้ผู้โทรทำในสิ่งที่ถูกต้อง นี่คือเหตุผลที่ฉันกล่าวว่าสิ่งนี้ถือว่าคุณควบคุมไซต์การโทรทั้งหมด


4
Object.assign ไม่คัดลอกวัตถุในระดับลึก ดูdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Object.assign () คัดลอกค่าคุณสมบัติ "หากค่าต้นทางเป็นการอ้างอิงถึงอ็อบเจ็กต์ค่านั้นจะคัดลอกเฉพาะค่าอ้างอิงนั้นเท่านั้น"
Greg Somers

ขวา. นี่เป็นทางเลือกอื่นที่ไม่เกี่ยวข้องกับการคัดลอกในระดับลึก ฉันจะอัปเดตคำตอบให้ชัดเจนยิ่งขึ้นเกี่ยวกับเรื่องนี้
Frank Tan

107

แทนที่จะใช้สิ่งนี้สำหรับสำเนาลึก

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


66
ใช้งานได้เฉพาะเมื่อคุณไม่จำเป็นต้องโคลนฟังก์ชัน JSON จะละเว้นฟังก์ชันทั้งหมดดังนั้นคุณจะไม่มีในโคลน
Noland

7
นอกเหนือจากฟังก์ชั่นแล้วคุณจะมีปัญหากับการไม่ได้กำหนดและเป็นโมฆะโดยใช้วิธีนี้
James Heazlewood

3
นอกจากนี้คุณจะมีปัญหากับคลาสที่ผู้ใช้กำหนดเองเนื่องจากโซ่ต้นแบบไม่ได้ต่ออนุกรมกัน
Patrick Roberts

9
วิธีแก้ปัญหาของคุณโดยใช้ JSON serialization มีปัญหาบางอย่าง การทำเช่นนี้คุณจะสูญเสียคุณสมบัติ Javascript ที่ไม่มีประเภทเทียบเท่าใน JSON เช่น Function หรือ Infinity คุณสมบัติใด ๆ ที่กำหนดให้กับ undefined จะถูกละเว้นโดย JSON.stringify ทำให้พลาดในอ็อบเจ็กต์ที่โคลน นอกจากนี้วัตถุบางอย่างจะถูกแปลงเป็นสตริงเช่นวันที่ชุดแผนที่และอื่น ๆ อีกมากมาย
Jonathan Brizio

2
ฉันกำลังฝันร้ายอย่างน่าสยดสยองในการพยายามสร้างสำเนาที่แท้จริงของอาร์เรย์ของวัตถุ - วัตถุที่เป็นค่าข้อมูลโดยพื้นฐานแล้วไม่มีฟังก์ชัน หากนั่นคือสิ่งที่คุณต้องกังวลวิธีนี้ก็ใช้ได้ผลดี
Charlie

31

จาก MDN

หมายเหตุ: การแพร่กระจายไวยากรณ์ไปได้อย่างมีประสิทธิภาพลึกหนึ่งระดับในขณะที่คัดลอกอาร์เรย์ ดังนั้นจึงอาจไม่เหมาะสำหรับการคัดลอกอาร์เรย์หลายมิติตามที่ตัวอย่างต่อไปนี้แสดงให้เห็น (เหมือนกับ Object.assign () และไวยากรณ์การแพร่กระจาย)

โดยส่วนตัวแล้วฉันขอแนะนำให้ใช้ฟังก์ชัน cloneDeep ของ Lodashสำหรับการโคลนวัตถุ / อาร์เรย์หลายระดับ

นี่คือตัวอย่างการทำงาน:

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 ใช้งานไม่ได้สำหรับฉัน ในเบราว์เซอร์ (chrome 59.0 ซึ่งรองรับ ES6 ฉันได้รับUncaught SyntaxError: โทเค็นที่ไม่คาดคิด ...และในโหนด 8.9.3 ซึ่งรองรับ ES7 ฉันได้รับTypeError: ไม่ได้กำหนดไม่ใช่ฟังก์ชันที่
Achi Even-dar

@ AchiEven-dar ไม่ได้ทำไมคุณถึงมีข้อผิดพลาด คุณสามารถเรียกใช้รหัสนี้ได้โดยตรงใน stackoverflow โดยกดปุ่มสีน้ำเงินRun code snippetและควรรันอย่างถูกต้อง
Mina Luke

3
arr6 ก็ใช้ไม่ได้สำหรับฉันเช่นกัน ในเบราว์เซอร์ - chrome 65
yehonatan yehezkel

19

ฉันมักจะใช้สิ่งนี้:

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

ใช้JSON.stringifyและJSON.parseเป็นวิธีที่ดีที่สุด เนื่องจากการใช้ตัวดำเนินการกระจายเราจะไม่ได้รับคำตอบที่มีประสิทธิภาพเมื่อวัตถุ json มีวัตถุอื่นอยู่ภายใน เราจำเป็นต้องระบุสิ่งนั้นด้วยตนเอง


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
}

ความคิดเห็นอยู่ในรหัสสำหรับผู้ที่ต้องการคำอธิบาย
Wookies-Will-Code

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

1

เมื่อวันที่แล้วฉันเองก็หาคำตอบเหล่านี้โดยพยายามหาวิธีคัดลอกโครงสร้างที่ซับซ้อนในระดับลึกซึ่งอาจรวมถึงลิงก์แบบวนซ้ำ เนื่องจากฉันไม่พอใจกับสิ่งที่แนะนำมาก่อนฉันจึงใช้วงล้อนี้ด้วยตัวเอง และได้ผลดีทีเดียว หวังว่ามันจะช่วยใครบางคน

ตัวอย่างการใช้งาน:

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

TheClone = OriginalStruct.deep_copy();

โปรดดูที่https://github.com/latitov/JS_DeepCopyสำหรับตัวอย่างสดวิธีการใช้งานและ deep_print () อยู่ที่นั่น

หากคุณต้องการอย่างรวดเร็วนี่คือที่มาของฟังก์ชัน 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;
    }
}

ไชโย @!


1

นี่คือฟังก์ชัน deepClone ที่จัดการประเภทข้อมูลดั้งเดิมอาร์เรย์อ็อบเจ็กต์ฟังก์ชันทั้งหมด

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


1

นี่คืออัลกอริทึมสำเนาลึกของฉัน

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

คุณต้องตรวจสอบด้วยว่า 'obj [prop]! == null' เป็น typeof (null) ยังคืนค่า 'object' ด้วย
Pramod Mali
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.