Đây có phải là một cách tốt để sao chép một đối tượng trong ES6?


155

Googling cho "đối tượng nhân bản javascript" mang lại một số kết quả thực sự kỳ lạ, một số trong số chúng đã lỗi thời và một số quá phức tạp, không dễ như vậy:

let clone = {...original};

Có gì không ổn với điều này?


1
đây không phải là hợp pháp ES6. Nhưng nếu là người sói, đây không phải là một bản sao: cả bản sao và thuộc tính ban đầu của bạn đều hướng đến cùng một thứ. Ví dụ, original = { a: [1,2,3] }cung cấp cho bạn một bản sao với clone.anghĩa đen original.a. Sửa đổi thông qua một trong hai clonehoặc originalsửa đổi điều tương tự , vì vậy không, điều này là xấu =)
Mike 'Pomax' Kamermans

2
@AlbertoRivera Nó s kinda hợp lệ JavaScript, ở chỗ nó là một giai đoạn 2 đề nghị đó là có khả năng là một bổ sung trong tương lai để chuẩn JavaScript.
Frxstrem

@Frxstrem với câu hỏi là về ES6, đây không phải là JavaScript hợp lệ =)
Mike 'Pomax' Kamermans

3
Nông hay nhân bản sâu?
Felix Kling

2
Bạn nói đúng, ES6 không hợp lệ, ES9 hợp lệ . developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Khăn

Câu trả lời:


240

Điều này là tốt cho nhân bản nông . Sự lan truyền đối tượng là một phần tiêu chuẩn của ECMAScript 2018 .

Để nhân bản sâu, bạn sẽ cần một giải pháp khác .

const clone = {...original} nhân bản nông

const newobj = {...original, prop: newOne} để bất động thêm một chỗ dựa khác vào bản gốc và lưu trữ như một đối tượng mới.


18
Tuy nhiên, đây không phải là một bản sao nông? Như trong, tài sản không được nhân bản đệ quy, phải không? Do đó, original.innerObject === clone.innerObject và thay đổi gốc.innerObject.property sẽ thay đổi clone.innerObject.property.
milanio

18
vâng, đây là một bản sao nông. nếu bạn muốn một bản sao sâu, bạn phải sử dụngJSON.parse(JSON.stringify(input))
Mark Shust tại M.academy

8
/! \ JSON.parse (JSON.opesify (đầu vào)) làm rối tung ngày tháng, không xác định, ... Đây không phải là viên đạn bạc để nhân bản! Xem: maxpou.fr/immutability-js-without-l Library
Guillaume

1
Vì vậy, hack JSON.opesify () / JSON.parse () có thực sự là cách được đề xuất để sao chép sâu một đối tượng trong ES6 không? Tôi tiếp tục nhìn thấy nó được đề nghị. Phiền.
Solvitieg

3
@MarkShust JSON.parse(JSON.stringify(input))sẽ không hoạt động, bởi vì nếu có functionshoặc infinitydưới dạng giá trị, nó sẽ chỉ gán nullở vị trí của chúng. Nó sẽ chỉ hoạt động nếu các giá trị đơn giản literalsvà không functions.
dấu gạch chéo ngược

65

EDIT: Khi câu trả lời này được đăng, {...obj}cú pháp không có sẵn trong hầu hết các trình duyệt. Ngày nay, bạn sẽ ổn khi sử dụng nó (trừ khi bạn cần hỗ trợ IE 11).

Sử dụng 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 }

Tuy nhiên, điều này sẽ không tạo ra một bản sao sâu sắc. Cho đến nay vẫn chưa có cách nhân bản sâu.

EDIT: Như @Mike 'Pomax' Kamermans đã đề cập trong các bình luận, bạn có thể sao chép sâu các đối tượng đơn giản (ví dụ: không có nguyên mẫu, hàm hoặc tham chiếu vòng tròn) bằng cách sử dụng JSON.parse(JSON.stringify(input))


19
Có một, với điều kiện đối tượng của bạn là một đối tượng thực sự theo nghĩa đen và hoàn toàn là dữ liệu, trong trường hợp đó JSON.parse(JSON.stringify(input))là một bản sao sâu thích hợp. Tuy nhiên, các nguyên mẫu thời điểm, chức năng hoặc tham chiếu vòng tròn đang hoạt động, giải pháp đó không còn hoạt động.
Mike 'Pomax' Kamermans

@ Mike'Pomax'Kamermans Điều đó đúng. Mất chức năng cho getters và setters là khủng khiếp, mặc dù ...
Alberto Rivera

Nếu bạn cần một hàm chung để sao chép sâu bất kỳ đối tượng nào, hãy kiểm tra stackoverflow.com/a/13333781/560114 .
Matt Browne

1
Bây giờ có một cách để nhân bản sâu tự nhiên .
Dan Dascalescu

1
@DanDascalescu mặc dù là thử nghiệm nhưng có vẻ khá hứa hẹn. Cảm ơn bạn về thông tin!
Alberto Rivera

4

Nếu các phương thức bạn đã sử dụng không hoạt động tốt với các đối tượng liên quan đến các loại dữ liệu như Ngày , hãy thử điều này

Nhập khẩu _

import * as _ from 'lodash';

Đối tượng nhân bản sâu

myObjCopy = _.cloneDeep(myObj);

Chỉ cần import _ from 'lodash';là đủ. Nhưng +1 cho câu trả lời "không phát minh lại bánh xe".
rustyx

lodash là cồng kềnh. Thực sự không cần phải kéo vào lodash chỉ cho một bản sao sâu đơn giản. Rất nhiều giải pháp khác ở đây. Đây là một câu trả lời thực sự tồi tệ cho các nhà phát triển web muốn xây dựng một ứng dụng nạc.
Jason Rice

3

nếu bạn không muốn sử dụng json.parse (json.opesify (object)), bạn có thể tạo các bản sao khóa-giá trị đệ quy:

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

Nhưng cách tốt nhất là tạo một lớp có thể tự trả về một bản sao của nó

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

Theo dõi câu trả lời của @marcel, tôi thấy một số chức năng vẫn còn thiếu trên đối tượng nhân bản. ví dụ

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

trong đó trên MyObject tôi có thể sao chép phương thứcA nhưng phương thứcB đã bị loại trừ. Điều này xảy ra vì nó bị mất

enumerable: true

có nghĩa là nó đã không xuất hiện trong

for(let key in item)

Thay vào đó tôi chuyển sang

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

trong đó sẽ bao gồm các phím không đếm được.

Tôi cũng thấy rằng nguyên mẫu ( proto ) không được nhân bản. Cho rằng tôi đã kết thúc bằng cách sử dụng

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

PS: Thật khó chịu khi tôi không thể tìm thấy một hàm tích hợp để làm điều này.


1

Bạn có thể làm nó như thế này là tốt,

let copiedData = JSON.parse(JSON.stringify(data));

-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.

Nhưng Object.assign () không tạo bản sao sâu

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

Để khắc phục điều đó, chúng ta nên sử dụng vòng lặp nhân bản để kiểm tra từng giá trị của người dùng [khóa] và, nếu đó là một đối tượng, thì cũng sao chép cấu trúc của nó. Điều đó được gọi là một bản sao nhân bản sâu.

Có một thuật toán tiêu chuẩn để nhân bản sâu xử lý trường hợp trên và các trường hợp phức tạp hơn, được gọi là thuật toán nhân bản có cấu trúc . Để không phát minh lại bánh xe, chúng ta có thể sử dụng thực hiện hoạt động của nó ra khỏi thư viện JavaScript lodash phương pháp này được gọi là _.cloneDeep (obj) .


-1

Tất cả các phương pháp trên không xử lý nhân bản sâu các đối tượng nơi nó được lồng đến n cấp. Tôi đã không kiểm tra hiệu suất của nó so với những người khác nhưng nó ngắn và đơn giản.

Ví dụ đầu tiên bên dưới cho thấy nhân bản đối tượng bằng cách sử dụng bản sao Object.assignnào cho đến cấp đầu tiên.

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

Sử dụng cách tiếp cận dưới đây đối tượng nhân bản sâu

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 đã được đề cập như một phương pháp nhân bản sâu kém trong nhiều năm . Vui lòng kiểm tra câu trả lời trước đó, cũng như các câu hỏi liên quan. Ngoài ra, điều này không mới đối với ES6.
Dan Dascalescu

@DanDascalescu Tôi biết điều này và tôi nghĩ không nên sử dụng nó cho các đối tượng đơn giản. Những người khác cũng đã đề cập đến điều này trong câu trả lời của họ trong cùng một bài đăng và thậm chí là bình luận. Tôi nghĩ rằng nó không xứng đáng với một downvote.
Saksham

Chính xác - "người khác cũng đã đề cập" JSON.parse / stringify trong câu trả lời của họ. Tại sao đăng một câu trả lời khác với cùng một giải pháp?
Dan Dascalescu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.