นี่เป็นวิธีที่ดีในการโคลนวัตถุใน ES6 หรือไม่?


155

Googling สำหรับ "javascript clone object" นำผลลัพธ์ที่แปลกจริง ๆ มาใช้บางอันล้าสมัยอย่างสิ้นหวังและบางอันก็ซับซ้อนเกินไปไม่ง่ายเหมือน:

let clone = {...original};

มีอะไรผิดปกติกับเรื่องนี้หรือไม่?


1
นี่ไม่ใช่ ES6 ที่ถูกกฎหมาย แต่ถ้ามันไม่ได้อยู่นี่ไม่ใช่โคลน: ทั้งโคลนและคุณสมบัติดั้งเดิมของคุณชี้ไปที่สิ่งเดียวกันตอนนี้ ยกตัวอย่างเช่นoriginal = { a: [1,2,3] }ช่วยให้คุณโคลนที่มีตัวอักษรเป็นclone.a original.aการปรับเปลี่ยนอย่างใดอย่างหนึ่งcloneหรือoriginalปรับเปลี่ยนสิ่งเดียวกันดังนั้นไม่นี่คือไม่ดี =)
ไมค์ 'Pomax' Kamermans

2
@AlbertoRivera มันครับที่ถูกต้องใช้ JavaScript ในการที่จะเป็นขั้นตอนที่ 2เรื่องที่มีแนวโน้มที่จะได้รับนอกจากนี้ในอนาคตที่จะเป็นมาตรฐาน JavaScript
Frxstrem

@Frxstrem กับคำถามเกี่ยวกับ ES6 นี้ไม่ถูกต้อง JavaScript =)
Mike 'Pomax' Kamermans

3
โคลนตื้นหรือลึก?
เฟลิกซ์คลิง

2
คุณกำลังทางขวาก็ไม่ถูกต้อง ES6 ก็ES9 ที่ถูกต้อง developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
......

คำตอบ:


240

นี้เป็นสิ่งที่ดีสำหรับการโคลนตื้น การแพร่กระจายของวัตถุเป็นส่วนหนึ่งของมาตรฐาน ECMAScript 2018

สำหรับการโคลนนิ่งแบบลึกคุณจะต้องมีโซลูชันอื่น

const clone = {...original} โคลนที่ตื้น

const newobj = {...original, prop: newOne} การเพิ่มเสาอีกอันหนึ่งให้กับรูปแบบเดิมและเก็บไว้เป็นวัตถุใหม่


18
อย่างไรก็ตามนี่ไม่ใช่เพียงโคลนโคลนเท่านั้น? ในขณะที่คุณสมบัติไม่ได้ถูกโคลนซ้ำพวกเขา? ดังนั้น original.innerObject === clone.innerObject และการเปลี่ยน original.innerObject.property จะเปลี่ยน clone.innerObject.property
milanio

18
ใช่นี่เป็นโคลนที่ตื้น ถ้าคุณต้องการโคลนที่ลึกคุณต้องใช้JSON.parse(JSON.stringify(input))
Mark Shust ที่ M.academy

8
/! \ JSON.parse (JSON.stringify (อินพุต)) ทำให้วันที่ยุ่งเหยิงไม่ได้กำหนด ... มันไม่ใช่กระสุนเงินสำหรับการโคลนนิ่ง! ดู: maxpou.fr/immutability-js-without-library
Guillaume

1
ดังนั้น JSON.stringify () / JSON.parse () คือวิธีที่แนะนำในการโคลนวัตถุใน ES6 หรือไม่ ฉันเห็นมันแนะนำเสมอ รบกวน
Solvitieg

3
@ MarkShust JSON.parse(JSON.stringify(input))จะไม่ทำงานเพราะหากมี functionsหรือinfinityเป็นค่าก็จะมอบหมายnullในสถานที่ของพวกเขา มันจะทำงานหากค่าที่มีความเรียบง่ายเป็นและไม่ได้literals functions
backslashN

66

แก้ไข: เมื่อคำตอบนี้ถูกโพสต์, {...obj}ไวยากรณ์ไม่สามารถใช้ได้ในเบราว์เซอร์ส่วนใหญ่ ทุกวันนี้คุณควรใช้มันให้ดี (เว้นแต่คุณจะต้องรองรับ IE 11)

ใช้ Object.assign

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

อย่างไรก็ตามสิ่งนี้จะไม่ทำการโคลนแบบลึก ยังไม่มีวิธีการโคลนแบบพื้นเมืองในขณะนี้

แก้ไข: ตามที่ @Mike 'Pomax' Kamermans ที่กล่าวถึงในความคิดเห็นคุณสามารถโคลนวัตถุง่าย ๆ ได้ (เช่นไม่มีต้นแบบฟังก์ชันหรือการอ้างอิงแบบวงกลม) โดยใช้ JSON.parse(JSON.stringify(input))


19
มีหนึ่งให้วัตถุของคุณเป็นตัวอักษรวัตถุที่แท้จริงและข้อมูลอย่างหมดจดซึ่งในกรณีที่JSON.parse(JSON.stringify(input))เป็นโคลนลึกที่เหมาะสม อย่างไรก็ตามโมเมนต์ต้นแบบฟังก์ชันหรือการอ้างอิงแบบวงกลมอยู่ในระหว่างเล่นโซลูชันนั้นไม่ทำงานอีกต่อไป
Kamermans ของไมค์ 'Pomax'

@ Mike'Pomax'Kamermans นั่นเป็นเรื่องจริง การสูญเสียฟังก์ชันการทำงานของผู้ให้บริการและผู้ตั้งค่าเป็นสิ่งที่เลวร้าย แต่ ...
อัลเบอร์โตริเวร่า

หากคุณต้องการฟังก์ชั่นทั่วไปเพื่อโคลนลึกวัตถุใด ๆ ตรวจสอบstackoverflow.com/a/13333781/560114
Matt Browne

1
ขณะนี้มีวิธีการทำโคลนลึกกำเนิด
Dan Dascalescu

1
@DanDascalescu แม้ว่ามันจะเป็นการทดลอง แต่มันก็ดูดี ขอบคุณสำหรับข้อมูล!
Alberto Rivera

4

หากวิธีการที่คุณใช้ทำงานได้ไม่ดีกับวัตถุที่เกี่ยวข้องกับชนิดข้อมูลเช่นวันที่ให้ลองวิธีนี้

นำเข้า _

import * as _ from 'lodash';

วัตถุโคลนนิ่งลึก

myObjCopy = _.cloneDeep(myObj);

เพียงimport _ from 'lodash';ก็เพียงพอ แต่ +1 สำหรับคำตอบ "อย่าปรับแต่งวงล้อ"
rustyx

Lodash ป่อง จริง ๆ ไม่จำเป็นต้องดึงใน lodash เพียงสำเนาลึก ๆ ง่าย ๆ โซลูชั่นอื่น ๆ อีกมากมายที่นี่ นี่เป็นคำตอบที่แย่มากสำหรับนักพัฒนาเว็บที่ต้องการสร้างแอพแบบลีน
Jason Rice

3

หากคุณไม่ต้องการใช้ json.parse (json.stringify (object)) คุณสามารถสร้างสำเนาคีย์ - ค่าแบบเรียกซ้ำ:

function copy(item){
  let result = null;
  if(!item) return result;
  if(Array.isArray(item)){
    result = [];
    item.forEach(element=>{
      result.push(copy(element));
    });
  }
  else if(item instanceof Object && !(item instanceof Function)){ 
    result = {};
    for(let key in item){
      if(key){
        result[key] = copy(item[key]);
      }
    }
  }
  return result || item;
}

แต่วิธีที่ดีที่สุดคือการสร้างคลาสที่สามารถคืนค่าโคลนของมันเอง

class MyClass{
    data = null;
    constructor(values){ this.data = values }
    toString(){ console.log("MyClass: "+this.data.toString(;) }
    remove(id){ this.data = data.filter(d=>d.id!==id) }
    clone(){ return new MyClass(this.data) }
}

2

ต่อจากคำตอบโดย @marcel ฉันพบว่าบางฟังก์ชั่นยังคงหายไปในวัตถุที่ถูกโคลน เช่น

function MyObject() {
  var methodAValue = null,
      methodBValue = null

  Object.defineProperty(this, "methodA", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    },
    enumerable: true
  });

  Object.defineProperty(this, "methodB", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    }
  });
}

ที่ MyObject ฉันสามารถโคลน methodA แต่ methodB ได้รับการยกเว้น สิ่งนี้เกิดขึ้นเพราะมันหายไป

enumerable: true

ซึ่งหมายความว่ามันไม่ปรากฏใน

for(let key in item)

แต่ฉันเปลี่ยนไปเป็น

Object.getOwnPropertyNames(item).forEach((key) => {
    ....
  });

ซึ่งจะรวมถึงคีย์ที่ไม่สามารถนับได้

ฉันก็พบว่าต้นแบบ ( โปรโต ) ไม่ได้ถูกโคลน สำหรับที่ฉันลงเอยด้วยการใช้

if (obj.__proto__) {
  copy.__proto__ = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
}

PS: น่าผิดหวังที่ฉันไม่สามารถหาฟังก์ชั่นในตัวเพื่อทำสิ่งนี้



-1
We can do that with two way:
1- First create a new object and replicate the structure of the existing one by iterating 
 over its properties and copying them on the primitive level.

let user = {
     name: "John",
     age: 30
    };

    let clone = {}; // the new empty object

    // let's copy all user properties into it
    for (let key in user) {
      clone[key] = user[key];
    }

    // now clone is a fully independant clone
    clone.name = "Pete"; // changed the data in it

    alert( user.name ); // still John in the original object

2- Second we can use the method Object.assign for that 
    let user = { name: "John" };
    let permissions1 = { canView: true };
    let permissions2 = { canEdit: true };

    // copies all properties from permissions1 and permissions2 into user
    Object.assign(user, permissions1, permissions2);

  -Another example

    let user = {
      name: "John",
      age: 30
    };

    let clone = Object.assign({}, user);
It copies all properties of user into the empty object and returns it. Actually, the same as the loop, but shorter.

แต่ Object.assign () ไม่สร้างการโคลนแบบลึก

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, same object

// user and clone share sizes
user.sizes.width++;       // change a property from one place
alert(clone.sizes.width); // 51, see the result from the other one

ในการแก้ไขปัญหานั้นเราควรใช้การวนลูปที่ตรวจสอบแต่ละค่าของผู้ใช้ [คีย์] และถ้าเป็นวัตถุให้ทำซ้ำโครงสร้างของมันเช่นกัน ที่เรียกว่า "การโคลนนิ่งลึก"

มีขั้นตอนวิธีการมาตรฐานสำหรับการโคลนลึกเป็นเรื่องที่จับกรณีดังกล่าวข้างต้นและกรณีที่ซับซ้อนมากขึ้นเรียกว่าโครงสร้างขั้นตอนวิธีการโคลน เพื่อไม่ให้บูรณาการล้อเราสามารถใช้การดำเนินการในการทำงานของมันจากห้องสมุด JavaScript lodashวิธีการที่เรียกว่า_.cloneDeep (obj)


-1

วิธีการทั้งหมดข้างต้นไม่สามารถจัดการกับการโคลนนิ่งของวัตถุที่อยู่ในระดับ n ฉันไม่ได้ตรวจสอบประสิทธิภาพของมันเหนือคนอื่น ๆ แต่มันสั้นและง่าย

ตัวอย่างแรกด้านล่างแสดงการโคลนวัตถุโดยใช้Object.assignโคลนใดจนถึงระดับแรก

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

newPerson = Object.assign({},person);
newPerson.skills.lang = 'angular';
console.log(newPerson.skills.lang); //logs Angular

การใช้วัตถุโคลนนิ่งแบบลึกเข้าใกล้ด้านล่าง

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

anotherNewPerson = JSON.parse(JSON.stringify(person));
anotherNewPerson.skills.lang = 'angular';
console.log(person.skills.lang); //logs javascript


JSON.parse / stringify ได้รับการกล่าวถึงเป็นวิธีการที่ไม่ดีโคลนลึกสำหรับปีที่ผ่านมา โปรดตรวจสอบคำตอบก่อนหน้ารวมถึงคำถามที่เกี่ยวข้อง นอกจากนี้นี่ไม่ใช่สิ่งใหม่สำหรับ ES6
Dan Dascalescu

@DanDascalescu ฉันรู้เรื่องนี้และฉันคิดว่ามันไม่น่าจะมีปัญหาในการใช้มันกับวัตถุอย่างง่าย คนอื่น ๆ ได้พูดถึงเรื่องนี้ในคำตอบของพวกเขาในโพสต์เดียวกันและแม้กระทั่งความคิดเห็น ฉันคิดว่ามันไม่สมควรได้รับการโหวต
Saksham

ตรง - "คนอื่นพูดถึง" JSON.parse / stringify ในคำตอบของพวกเขา เหตุใดจึงโพสต์อีกคำตอบด้วยโซลูชันเดียวกัน
Dan Dascalescu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.