Sao chép các đối tượng trong JavaScript
Đối tượng là khối cơ bản của JavaScript. Một đối tượng là một tập hợp các thuộc tính và một thuộc tính là một liên kết giữa khóa (hoặc tên) và một giá trị. Hầu hết tất cả các đối tượng trong JavaScript đều là các version củaObject
nằm trên cùng của chuỗi nguyên mẫu. Như bạn đã biết, toán tử gán không tạo bản sao của một đối tượng, nó chỉ gán một tham chiếu cho nó, hãy xem đoạn mã sau:
let obj = { a: 1, b: 2, }; let copy = obj; obj.a = 5; console.log(copy.a); // Result // a = 5;
Biến obj
là một containers cho đối tượng mới được khởi tạo. Biến copy
đang trỏ đến cùng một đối tượng và là một tham chiếu đến đối tượng đó. Vì vậy, về cơ bản đối tượng { a: 1, b: 2, }
này đang nói: Hiện tại có hai cách để có được quyền truy cập vào tôi. Bạn phải chuyển qua biến obj
hoặc biến copy
theo cách nào đó mà bạn vẫn tiếp cận được với tôi và bất cứ điều gì bạn làm với tôi qua những cách này (cổng) sẽ ảnh hưởng đến tôi.
Tính bất biến được nói rộng rãi ngày nay và bạn phải lắng nghe lời kêu gọi này! Phương pháp này loại bỏ bất kỳ dạng bất biến nào và có thể dẫn đến lỗi nếu đối tượng root được phần khác trong mã của bạn sử dụng.
Cách sao chép đồ vật ngây thơ
Cách thức đơn giản của việc sao chép các đối tượng là lặp qua đối tượng root và sao chép lần lượt từng thuộc tính. Hãy xem mã này:
function copy(mainObj) { let objCopy = {}; // objCopy will store a copy of the mainObj let key; for (key in mainObj) { objCopy[key] = mainObj[key]; // copies each property to the objCopy object } return objCopy; } const mainObj = { a: 2, b: 5, c: { x: 7, y: 4, }, } console.log(copy(mainObj));
Vấn đề vốn có
- Đối tượng
objCopy
có phương thứcObject.prototype
mới khác với phương thức nguyên mẫu của đối tượngmainObj
, phương thức này không phải là những gì ta muốn. Ta muốn có một bản sao chính xác của đối tượng root . - Bộ mô tả thuộc tính không được sao chép. Bộ mô tả "có thể ghi" với giá trị được đặt là false sẽ đúng trong đối tượng
objCopy
. - Đoạn mã trên chỉ sao chép các thuộc tính có thể liệt kê của
mainObj
. - Nếu một trong các thuộc tính trong đối tượng root là chính một đối tượng, thì nó sẽ được chia sẻ giữa bản sao và bản root làm cho các thuộc tính tương ứng của chúng trỏ đến cùng một đối tượng.
Đối tượng sao chép nông
Một đối tượng được cho là được sao chép cạn khi các thuộc tính cấp cao nhất của nguồn được sao chép mà không có bất kỳ tham chiếu nào và tồn tại một thuộc tính nguồn có giá trị là một đối tượng và được sao chép dưới dạng tham chiếu. Nếu giá trị nguồn là một tham chiếu đến một đối tượng, nó chỉ sao chép giá trị tham chiếu đó vào đối tượng đích.
Bản sao cạn sẽ sao chép các thuộc tính cấp cao nhất, nhưng đối tượng lồng nhau được chia sẻ giữa bản root (nguồn) và bản sao (đích).
Sử dụng phương thức Object.assign ()
Phương thức Object.assign () được sử dụng để sao chép các giá trị của tất cả các thuộc tính riêng có thể liệt kê từ một hoặc nhiều đối tượng nguồn sang một đối tượng đích.
let obj = { a: 1, b: 2, }; let objCopy = Object.assign({}, obj); console.log(objCopy); // Result - { a: 1, b: 2 }
Chà, điều này thực hiện công việc cho đến nay. Ta đã tạo một bản sao của obj
. Hãy xem liệu tính bất biến có tồn tại không:
let obj = { a: 1, b: 2, }; let objCopy = Object.assign({}, obj); console.log(objCopy); // result - { a: 1, b: 2 } objCopy.b = 89; console.log(objCopy); // result - { a: 1, b: 89 } console.log(obj); // result - { a: 1, b: 2 }
Trong đoạn mã trên, ta đã thay đổi giá trị của thuộc tính 'b'
trong đối tượng objCopy
thành 89
và khi ta đăng nhập đối tượng objCopy
đã sửa đổi trong console , các thay đổi chỉ áp dụng cho objCopy
. Dòng mã cuối cùng kiểm tra xem đối tượng obj
vẫn còn nguyên vẹn và không thay đổi. Điều này ngụ ý rằng ta đã tạo thành công một bản sao của đối tượng nguồn mà không có bất kỳ tham chiếu nào đến nó.
Cạm bẫy của Object.assign ()
Không quá nhanh! Trong khi ta đã tạo thành công một bản sao và mọi thứ dường như hoạt động tốt, hãy nhớ rằng ta đã thảo luận về việc sao chép nông cạn chứ? Hãy xem ví dụ này:
let obj = { a: 1, b: { c: 2, }, } let newObj = Object.assign({}, obj); console.log(newObj); // { a: 1, b: { c: 2} } obj.a = 10; console.log(obj); // { a: 10, b: { c: 2} } console.log(newObj); // { a: 1, b: { c: 2} } newObj.a = 20; console.log(obj); // { a: 10, b: { c: 2} } console.log(newObj); // { a: 20, b: { c: 2} } newObj.b.c = 30; console.log(obj); // { a: 10, b: { c: 30} } console.log(newObj); // { a: 20, b: { c: 30} } // Note: newObj.b.c = 30; Read why..
Tại sao obj.bc = 30?
Chà, đó là một cạm bẫy của Object.assign()
. Object.assign
chỉ tạo ra các bản sao nông. Cả newObj.b
và obj.b
đều chia sẻ cùng một tham chiếu đến đối tượng vì các bản sao riêng lẻ không được tạo, thay vào đó, một tham chiếu đến đối tượng đã được sao chép. Mọi thay đổi được thực hiện đối với bất kỳ thuộc tính nào của đối tượng đều áp dụng cho tất cả các tham chiếu sử dụng đối tượng. Làm thế nào ta có thể khắc phục điều này? Tiếp tục đọc… ta có một bản sửa lỗi trong phần tiếp theo.
Lưu ý: Không thể sao chép các thuộc tính trên chuỗi nguyên mẫu và các thuộc tính không liệt kê. Xem tại đây:
let someObj = { a: 2, } let obj = Object.create(someObj, { b: { value: 2, }, c: { value: 3, enumerable: true, }, }); let objCopy = Object.assign({}, obj); console.log(objCopy); // { c: 3 }
someObj
nằm trên chuỗi nguyên mẫu của obj nên nó sẽ không bị sao chép.-
property b
làproperty b
không liệt kê được. -
property c
có một bộ mô tả thuộc tính có thể liệt kê cho phép nó có thể liệt kê được. Đó là lý do tại sao nó đã được sao chép.
Đối tượng sao chép sâu
Bản sao sâu sẽ nhân bản mọi đối tượng mà nó gặp phải. Bản sao và đối tượng root sẽ không chia sẻ bất cứ điều gì, vì vậy nó sẽ là bản sao của bản root . Đây là bản sửa lỗi cho sự cố ta gặp phải khi sử dụng Object.assign()
. Hãy cùng khám phá.
Sử dụng JSON.parse (JSON.stringify (object));
Điều này khắc phục sự cố ta gặp phải trước đó. Bây giờ newObj.b
có một bản sao và không phải là một tài liệu tham khảo! Đây là một cách để sao chép sâu các đối tượng. Đây là một ví dụ:
let obj = { a: 1, b: { c: 2, }, } let newObj = JSON.parse(JSON.stringify(obj)); obj.b.c = 20; console.log(obj); // { a: 1, b: { c: 20 } } console.log(newObj); // { a: 1, b: { c: 2 } } (New Object Intact!)
Bất biến: ✓
Cạm bẫy
Rất tiếc, không thể sử dụng phương pháp này để sao chép các phương thức đối tượng do user xác định. Xem bên dưới.
Sao chép các phương pháp đối tượng
Phương thức là một thuộc tính của một đối tượng là một hàm. Trong các ví dụ cho đến nay, ta chưa sao chép một đối tượng bằng một phương thức. Hãy thử điều đó ngay bây giờ và sử dụng các phương pháp ta đã học để tạo bản sao.
let obj = { name: 'scotch.io', exec: function exec() { return true; }, } let method1 = Object.assign({}, obj); let method2 = JSON.parse(JSON.stringify(obj)); console.log(method1); //Object.assign({}, obj) /* result { exec: function exec() { return true; }, name: "scotch.io" } */ console.log(method2); // JSON.parse(JSON.stringify(obj)) /* result { name: "scotch.io" } */
Kết quả cho thấy rằng Object.assign()
được dùng để sao chép các phương thức trong khi JSON.parse(JSON.stringify(obj))
không thể được sử dụng.
Sao chép các đối tượng hình tròn
Đối tượng tròn là đối tượng có các thuộc tính tham chiếu đến chính nó. Hãy sử dụng các phương pháp sao chép đối tượng mà ta đã học cho đến nay để tạo bản sao của một đối tượng hình tròn và xem nó có hoạt động không.
Sử dụng JSON.parse (JSON.stringify (đối tượng))
Hãy thử JSON.parse(JSON.stringify(object))
:
// circular object let obj = { a: 'a', b: { c: 'c', d: 'd', }, } obj.c = obj.b; obj.e = obj.a; obj.b.c = obj.c; obj.b.d = obj.b; obj.b.e = obj.b.c; let newObj = JSON.parse(JSON.stringify(obj)); console.log(newObj);
Đây là kết quả:
JSON.parse(JSON.stringify(obj))
rõ ràng không hoạt động cho các đối tượng hình tròn.
Sử dụng Object.assign ()
Hãy thử Object.assign()
:
// circular object let obj = { a: 'a', b: { c: 'c', d: 'd', }, } obj.c = obj.b; obj.e = obj.a; obj.b.c = obj.c; obj.b.d = obj.b; obj.b.e = obj.b.c; let newObj2 = Object.assign({}, obj); console.log(newObj2);
Đây là kết quả:
Object.assign()
hoạt động tốt khi sao chép nông các đối tượng hình tròn nhưng sẽ không hoạt động khi sao chép sâu. Hãy thoải mái khám phá circular object tree
trên console trình duyệt của bạn. Tôi chắc rằng bạn sẽ tìm thấy rất nhiều công việc thú vị đang diễn ra ở đó.
Sử dụng phần tử Spread (…)
ES6 đã có các phần tử còn lại để gán cấu trúc mảng và rải các phần tử cho các ký tự mảng được triển khai. Hãy xem cách triển khai phần tử spread trên một mảng tại đây:
const array = [ "a", "c", "d", { four: 4 }, ]; const newArray = [...array]; console.log(newArray); // Result // ["a", "c", "d", { four: 4 }]
Thuộc tính spread cho các ký tự đối tượng hiện là đề xuất Giai đoạn 3 cho ECMAScript . Trải rộng các thuộc tính trong bộ khởi tạo đối tượng sao chép các thuộc tính có thể liệt kê của riêng từ một đối tượng nguồn vào đối tượng đích. Ví dụ dưới đây cho thấy việc sao chép một đối tượng sẽ dễ dàng như thế nào khi đề xuất đã được chấp nhận.
let obj = { one: 1, two: 2, } let newObj = { ...z }; // { one: 1, two: 2 }
Lưu ý: Điều này sẽ chỉ hiệu quả đối với bản sao cạn
Kết luận
Việc sao chép các đối tượng trong JavaScript có thể khá khó khăn, đặc biệt nếu bạn mới làm quen với JavaScript và không biết cách sử dụng ngôn ngữ của bạn . Hy vọng rằng bài viết này đã giúp bạn hiểu và tránh những cạm bẫy trong tương lai mà bạn có thể gặp phải khi sao chép đối tượng. Nếu bạn có thư viện hoặc đoạn mã nào đạt được kết quả tốt hơn, vui lòng chia sẻ với cộng đồng. Chúc bạn viết mã vui vẻ!
Các tin liên quan
Lời hứa của JavaScript dành cho người giả2020-09-15
Cách sử dụng API tìm nạp JavaScript để lấy dữ liệu
2020-09-15
Cách mã hóa và giải mã chuỗi với Base64 trong JavaScript
2020-09-15
Toán tử đơn nguyên JavaScript: Đơn giản và hữu ích
2020-09-15
Hiểu Hoisting trong JavaScript
2020-09-15
5 Mẹo để Viết Điều kiện Tốt hơn trong JavaScript
2020-09-15
Hiểu Vòng lặp sự kiện, Gọi lại, Hứa hẹn và Không đồng bộ / Chờ đợi trong JavaScript
2020-09-10
Bốn phương pháp để tìm kiếm thông qua các mảng trong JavaScript
2020-09-09
Sử dụng phương thức Array.find trong JavaScript
2020-09-09
split () Phương thức chuỗi trong JavaScript
2020-09-09