Cách sao chép một phiên bản lớp javascript ES6


96

Làm cách nào để sao chép một phiên bản lớp Javascript bằng ES6.

Tôi không quan tâm đến các giải pháp dựa trên jquery hoặc $ extension.

Tôi đã thấy các cuộc thảo luận khá cũ về việc sao chép đối tượng cho thấy rằng vấn đề khá phức tạp, nhưng với ES6, một giải pháp rất đơn giản sẽ tự xuất hiện - tôi sẽ đưa ra bên dưới và xem mọi người có nghĩ rằng nó thỏa đáng hay không.

chỉnh sửa: người ta cho rằng câu hỏi của tôi là một bản sao; Tôi đã thấy câu trả lời đó nhưng nó đã được 7 năm tuổi và liên quan đến các câu trả lời rất phức tạp bằng cách sử dụng js trước ES6. Tôi gợi ý rằng câu hỏi của tôi, cho phép ES6, có một giải pháp đơn giản hơn đáng kể.


2
Nếu bạn có câu trả lời mới cho câu hỏi cũ trên Stack Overflow, vui lòng thêm câu trả lời đó vào câu hỏi ban đầu, đừng chỉ tạo một câu mới.
Heretic Monkey

1
Tôi thấy vấn đề Tom đang / đang gặp phải vì các cá thể lớp ES6 hoạt động khác với các Đối tượng "thông thường".
CherryNerd

2
Ngoài ra, phần đầu tiên của mã trong câu trả lời chấp nhận của bạn "có thể trùng lặp" cung cấp thực sự bị treo khi tôi cố gắng chạy nó trên một thể hiện của một lớp ES6
CherryNerd

Tôi nghĩ rằng đây không phải là một bản sao, bởi vì mặc dù cá thể lớp ES6 là một đối tượng, nhưng không phải mọi đối tượng đều là cá thể lớp ES6 và do đó câu hỏi khác không giải quyết vấn đề của câu hỏi này.
Tomáš Zato - Phục hồi Monica

5
Nó không phải là một bản sao. Câu hỏi khác là về các đơn vị thuần Objectđược sử dụng làm chủ sở hữu dữ liệu. Đây là vấn đề về ES6 classes và vấn đề để không làm mất thông tin loại lớp. Nó cần một giải pháp khác.
flori

Câu trả lời:


111

Nó là phức tạp; Tôi đã cố gắng rất nhiều! Cuối cùng, một lớp lót này đã hoạt động cho các phiên bản lớp ES6 tùy chỉnh của tôi:

let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)

Nó tránh thiết lập nguyên mẫu vì họ nói rằng nó làm chậm mã rất nhiều.

Nó hỗ trợ các biểu tượng nhưng không hoàn hảo cho getters / setters và không hoạt động với các thuộc tính không thể liệt kê (xem Object.assign () docs ). Ngoài ra, việc nhân bản các lớp bên trong cơ bản (như Array, Date, RegExp, Map, v.v.) đáng buồn là thường có vẻ cần một số xử lý riêng lẻ.

Kết luận: Đó là một mớ hỗn độn. Hãy hy vọng rằng một ngày nào đó sẽ có một chức năng sao chép nguyên bản và sạch sẽ.


1
Điều này sẽ không sao chép các phương thức tĩnh vì chúng không thực sự là các thuộc tính riêng có thể liệt kê được.
Ông Lavalamp

5
@ Mr.Lavalamp và làm cách nào bạn có thể sao chép (cũng như) các phương thức tĩnh?
flori

điều này sẽ phá hủy các mảng! Nó sẽ chuyển đổi tất cả các mảng thành các đối tượng có các phím "0", "1", ...
Vahid

1
@KeshaAntonov Bạn có thể tìm thấy giải pháp với các phương thức typeof và Array. Bản thân tôi thích sao chép tất cả các thuộc tính theo cách thủ công.
Vahid

1
Đừng mong đợi nó tính nhân bản là bản thân các đối tượng: jsbin.com/qeziwetexu/edit?js,console
jduhls

10
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Lưu ý các đặc điểm của Object.assign : nó thực hiện một bản sao cạn và không sao chép các phương thức của lớp.

Nếu bạn muốn có một bản sao sâu hoặc kiểm soát nhiều hơn bản sao thì có các chức năng sao chép lodash .


2
Object.createtạo đối tượng mới với nguyên mẫu được chỉ định, tại sao không chỉ const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). Ngoài ra, các phương thức của lớp cũng sẽ được sao chép.
barbatus

1
@barbatus Tuy nhiên, sử dụng nguyên mẫu sai Blah.prototype != instanceOfBlah,. Bạn nên sử dụngObject.getPrototypeOf(instanceOfBlah)
Bergi

1
@Bergi không, cá thể lớp ES6 không phải lúc nào cũng có nguyên mẫu. Kiểm tra codepen.io/techniq/pen/qdZeZm rằng nó cũng hoạt động với phiên bản.
barbatus

@barbatus Xin lỗi, sao? Tôi không làm theo. Tất cả các phiên bản đều có một nguyên mẫu, đó là điều tạo nên chúng. Hãy thử mã từ câu trả lời của flori.
Bergi

1
@Bergi Tôi nghĩ nó phụ thuộc vào cấu hình Babel hoặc cái gì đó. Tôi hiện đang triển khai một ứng dụng gốc phản ứng và các phiên bản không có thuộc tính kế thừa nào có nguyên mẫu rỗng ở đó. Cũng như bạn có thể thấy tại đây developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… có thể getPrototypeOf trả về null.
barbatus

3

Bạn không nên tạo các phần mở rộng của Nguyên mẫu, Điều này sẽ dẫn đến các vấn đề khi bạn thực hiện các thử nghiệm trên mã / thành phần của mình. Các khuôn khổ kiểm tra đơn vị sẽ không tự động giả định các phần mở rộng nguyên mẫu của bạn. Vì vậy, nó không phải là một thực hành tốt. Có nhiều lời giải thích hơn về các phần mở rộng nguyên mẫu tại đây Tại sao việc mở rộng các đối tượng gốc là một phương pháp không tốt?

Để sao chép các đối tượng trong JavaScript không có một cách đơn giản hay dễ hiểu. Đây là trường hợp đầu tiên sử dụng "Bản sao nông":

1 -> Phân thân nông:

class Employee {
    constructor(first, last, street) {
        this.firstName = first;
        this.lastName = last;
        this.address = { street: street };
    }

    logFullName() {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

let original = new Employee('Cassio', 'Seffrin', 'Street A, 23');
let clone =  Object.assign({},original); //object.assing() method
let cloneWithPrototype Object.create(Object.getPrototypeOf(original)), original) //  the clone will inherit the prototype methods of the original.
let clone2 = { ...original }; // the same of object assign but shorter sintax using "spread operator"
clone.firstName = 'John';
clone.address.street = 'Street B, 99'; //will not be cloned

Các kết quả:

original.logFullName ():

kết quả: Cassio Seffrin

clone.logFullName ():

kết quả: John Seffrin

original.address.street;

result: 'Street B, 99' // thông báo rằng đối tượng con ban đầu đã được thay đổi

Lưu ý: Nếu cá thể có các bao đóng là thuộc tính riêng, phương thức này sẽ không bao bọc nó. ( đọc thêm về bao đóng ) Và thêm vào đó, đối tượng phụ "địa chỉ" sẽ không bị sao chép.

clone.logFullName ()

sẽ không làm việc.

cloneWithPrototype.logFullName ()

sẽ hoạt động, vì bản sao cũng sẽ sao chép Nguyên mẫu của nó.

Để sao chép mảng với Object.assign:

let cloneArr = array.map((a) => Object.assign({}, a));

Sao chép mảng sử dụng sintax trải rộng ECMAScript:

let cloneArrSpread = array.map((a) => ({ ...a }));

2 -> Nhân bản sâu:

Để lưu trữ một tham chiếu đối tượng hoàn toàn mới, chúng ta có thể sử dụng JSON.stringify () để phân tích cú pháp đối tượng ban đầu dưới dạng chuỗi và sau khi phân tích cú pháp nó trở lại JSON.parse ().

let deepClone = JSON.parse(JSON.stringify(original));

Với bản sao sâu, các tham chiếu đến địa chỉ sẽ được lưu giữ. Tuy nhiên, các Nguyên mẫu deepClone sẽ bị đóng, do đó deepClone.logFullName () sẽ không hoạt động.

Thư viện bên thứ 3 ->:

Một tùy chọn khác sẽ là sử dụng thư viện của bên thứ 3 như loadash hoặc gạch dưới. Họ sẽ tạo một đối tượng mới và sao chép từng giá trị từ bản gốc sang đối tượng mới, giữ các tham chiếu của nó trong bộ nhớ.

Dấu gạch dưới: let cloneUnderscore = _ (original) .clone ();

Loadash bản sao: var cloneLodash = _.cloneDeep (bản gốc);

Nhược điểm của lodash hoặc gạch dưới là cần phải bao gồm một số thư viện bổ sung trong dự án của bạn. Tuy nhiên chúng là những lựa chọn tốt và cũng tạo ra kết quả hiệu suất cao.


1
Khi gán cho {}, bản sao sẽ không kế thừa bất kỳ phương thức nguyên mẫu nào của bản gốc. clone.logFullName()sẽ không hoạt động ở tất cả. Các Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)bạn đã trước là tốt, tại sao bạn thay đổi điều đó?
Bergi,

1
@Bergi cảm ơn sự đóng góp của bạn, tôi đang chỉnh sửa câu trả lời của mình ngay bây giờ, tôi đã thêm ý kiến ​​của bạn để sao chép nguyên mẫu!
Cassio Seffrin,

1
Tôi đánh giá cao sự giúp đỡ của bạn @Bergi, Vui lòng cho biết ý kiến ​​của bạn ngay bây giờ. Tôi đã hoàn thành ấn bản. Tôi nghĩ bây giờ câu trả lời đã bao gồm gần như tất cả các câu hỏi. Thks!
Cassio Seffrin,

1
Có, và giống như Object.assign({},original), nó không hoạt động.
Bergi,

1
đôi khi cách tiếp cận đơn giản hơn là tất cả những gì chúng ta cần. Nếu bạn không cần Nguyên mẫu và đối tượng phức tạp có thể chỉ là "bản sao = {...} gốc" có thể giải quyết vấn đề
Cassio Seffrin

0

Một lớp lót khác:

Hầu hết thời gian ... (hoạt động cho Date, RegExp, Map, String, Number, Array), btw, sao chép chuỗi, số là một chút buồn cười.

let clone = new obj.constructor(...[obj].flat())

cho những lớp không có hàm tạo bản sao:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)

0
class A {
  constructor() {
    this.x = 1;
  }

  y() {
    return 1;
  }
}

const a = new A();

const output = Object.getOwnPropertyNames(Object.getPrototypeOf(a)).concat(Object.getOwnPropertyNames(a)).reduce((accumulator, currentValue, currentIndex, array) => {
  accumulator[currentValue] = a[currentValue];
  return accumulator;
}, {});

nhập mô tả hình ảnh ở đây


-4

Ví dụ: bạn có thể sử dụng toán tử spread nếu bạn muốn sao chép một đối tượng có tên là Obj:

let clone = { ...obj};

Và nếu bạn muốn thay đổi hoặc thêm bất kỳ thứ gì vào đối tượng được nhân bản:

let clone = { ...obj, change: "something" };
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.