Copying Objects trong JavaScript

674

Objects là các fundamental blocks của JavaScript. Một object sẽ gồm các properties và 1 property là sự kết hợp giữa 1 key (hoặc name) và 1 giá trị. Viêc copy Objects trong javascript, và hầu hết tất cả các objects trong Javascript đều là instances của Object nằm ở đầu chuỗi prototype.

#Giới thiệu

Assignment operator không tạo bản sao chép của 1 object, chỉ chuyển 1 reference đến object như đoạn code sau:

—> Edit on JS Bin

Biến obj là 1 container cho object mới được khởi tạo. Biến copy chỉ đến cùng object và là 1 tham chiếu đến object đó. Như vậy, về cơ bản, object { a: 1, b: 2, } này cho biết: Có 2 cách để tiếp cận đến tôi. Bạn phải vượt qua biến obj hoặc biến copy và bất cứ điều bạn làm với tôi qua những con đường này (gateways) sẽ ảnh hưởng đến tôi.

Immutability cũng nói đến rất nhiều trong những ngày này và bạn còn phải tìm hiểu call này nữa! Method này sẽ loại bỏ các hình thức immutability bất kì và có thể gây ra bug nếu object gốc được sử dụng bởi phần khác trong code của bạn.

Có thể bạn muốn xem:

  12 Thư viện JavaScript trực quan hoá dữ liệu hot nhất năm 2019
  Hiểu về JavaScript không đồng bộ - Các event loop

#Cách Copy Objects

Cách để copy objects, thật thà nhất là loop qua object gốc và copy lần lượt mỗi property. Tham khảo đoạn code sau:

Inherent Issues
  1. Object objCopy có 1 method Object.prototype mới khác với method object prototype mainObj ,  đây lại không phải là điều chúng ta muốn. Chúng ta muốn 1 bản copy chính xác của object gốc
  2. Không copy các property descriptors. Mô tả “writable” với giá trị được set là false sẽ là true trong object objCopy
  3. Đoạn code trên chỉ sao chép các properties có thể đếm được mainObj.
  4. Nếu 1 trong những properties trong object gốc chính là 1 object, thì object đó sẽ được chia sẻ giữa bản copy và bản gốc, giúp cho các properties tương ứng sẽ trỏ về cùng object

#Copy Objects Cạn

Một object được xác định là sao chép cạn khi các nguồn properties cấp cao được sao chép mà không có bất kì tham chiếu nào và tồn tại 1 source property sở hữu giá trị là 1 object và được sao chép lại như 1 tham chiếu. Nếu giá trị source là 1 tham chiếu đến 1 object, thì nó chỉ sao chép giá trị tham chiếu đến object được nhắm đến.

Một bản copy cạn sẽ nhân đôi các properties cấp cao, nhưng object đã nest sẽ được chia sẻ giữa (nguồn) gốc và copy (đích)

Sử dụng method Object.assign()

Method Object.assign() được sử dụng để sao chép các giá trị của các properties có thể đếm được từ 1 hoặc nhiều source objects đến 1 object đích.

—> Edit on JS Bin

Chúng tôi đã tạo 1 bản copy của obj. Hãy xem liệu có tồn tại immutability hay không:

—> Edit on JS Bin

Trong đoạn code ở trên, chúng tôi đã thay đổi giá trị của property 'b' trong object objCopy thành 89 và khi chúng tôi log object objCopy đã được modified trong giao diện console, những thay đổi này chỉ áp dụng cho objCopy. Dòng code cuối cùng sẽ kiểm tra rằng liệu object obj có còn nguyên và không thay đổi hay không. Điều này cho thấy rằng chúng ta đã tạo bản copy của source object thành công mà không có bất kì tham chiếu nào.

Khó khăn tiềm ẩn của Object.assign()

Tuy đã tạo được bản copy và mọi thứ dường như đang hoạt động tốt nhưng bạn có nhớ chúng ta đã thảo luận về copy cạn ở trên không? Hãy xem ví dụ sau:

—> Edit on JS Bin

Tại sao obj.b.c = 30?

Đây là khó khăn tiềm ẩn của Object.assign()Object.assign tạo ra các bản copy cạn.  newObj.b và obj.b chia sẻ cùng tham chiếu đến object vì mỗi copies riêng không được tạo ra, thay vào đó 1 tham chiếu đến object đó lại được sao chép. Bất kì thay đổi nào dẫn đến property của object sẽ được áp dụng cho toàn bộ các tham chiếu sử dụng object đó. Làm thế nào để fix được? Chúng tôi sẽ đề cập trong phần tiếp theo…

Lưu ý rằng: Properties trong chuỗi prototype và các properities không đếm được sẽ không sao chép được:

—> Edit on JS Bin

  • someObj nằm trong chuỗi prototype của obj nên sẽ không copy được
  • property b là 1 property không đếm được
  • property c có 1 property descriptor đếm được, giúp nó cũng đếm được. Vì vậy property c sẽ copy được

#Copy Objects Sâu

Một bản copy sâu sẽ nhân đôi mỗi object mà nó gặp phải. Bản object sao chép & bản object gốc sẽ không chia sẻ mọi thứ, nên nó sẽ là bản copy của bản gốc. Dưới đây là cách fix vấn đề với Object.assign().

Sử dụng JSON.parse(JSON.stringify(object));

Cái này sẽ fix vấn đề mà chúng ta ở phần trước. Hiện newObj.b đang có 1 bản sao chép và không phải là 1 reference! Đây chính là cách để copy objects sâu.

Immutable: ✓

—> Edit on JS Bin

Khó khăn tiềm ẩn

Thật không may, không thể dùng method này để sao chép các object methods định vị ngườ dùng.

#Copy Object methods

Một method là 1 property của 1 object giữ vai trò là hàm. Trong các ví dụ trước, chúng ta chưa sao chép 1 object với 1 method nên hãy thử bằng ví dụ sau, đừng quên sử dụng các methods chúng ta đã học để tạo các copies.

—> Edit on JS Bin

Kết quả hiển thị rằng có thể sử dụng Object.assign() để copy methods trong khi JSON.parse(JSON.stringify(obj)) lại không thể dùng được.

#Copy Circular Objects

Circular objects là các objects có properties tham chiếu đến chính các properties đó. Hãy sử dụng các methods để copy những objects chúng ta đã nghiên cứu ở trước để tạo các bản copies của 1 circular object, xem thử chúng hoạt động ra sao nhé!

Sử dụng JSON.parse(JSON.stringify(object))

Thử dùng JSON.parse(JSON.stringify(object)):

—> Edit on JS Bin

Kết quả:

JSON.parse(JSON.stringify(obj)) rõ ràng không hoạt động với circular objects.

Video: [Q&A 1] Tối Ưu Hóa Tỉ Lệ Chuyển Đổi – Conversion Rate Optimization

Sử dụng Object.assign()

Thử dùng Object.assign():

—> Edit on JS Bin

Kết quả:

Object.assign() hoạt động tốt với việc copy circular objects cạn nhưng không hoạt động tốt với copy sâu. Thử dùng circular object tree trên browser console của bạn và biết đâu lại thấy thứ gì đó thú vị.

#Sử dụng Spread Elements ( … )

ES6 đã có các rest elements cho công việc destructure array và rải elements cho các array literals được implement. Xem qua quá trình imlement spread element trên 1 array ở đây:

—> Edit on JS Bin

Spread property cho các object literals hiện là Stage 3 proposal dành cho ECMAScript. Spread properties trong object initializers sao chép các properties đếm được của chính nó từ 1 source object vào object đích. Ví dụ sau sẽ cho thấy việc sao chép 1 object rất dễ một khi proposal đã được chấp nhận.

Lưu ý: Ví dụ này chỉ hiệu quả cho copy cạn

#Kết luận

Sao chép objects trong JavaScript có thể làm bạn khá nản, đặc biệt là nếu bạn mới tìm hiểu JavaScript. Hy vọng rằng bài viết này sẽ giúp bạn hiểu rõ hơn và tránh những khó khăn tiềm ẩn mà bạn có thể gặp phải khi sao chép các objects.

TopDev via Scotch.io