Sao chép cấu trúc dữ liệu là một công việc phổ biến khi làm việc với các đối tượng và mảng trong JavaScript. Tuy nhiên, các nhà phát triển thường gặp khó khăn khi quyết định giữa Shallow Copy và Deep Copy, việc hiểu sai sự khác biệt có thể dẫn đến những tác dụng phụ không mong muốn trong mã của bạn.
Shallow Copy là gì?
Một bản Shallow Copy chỉ tạo ra một đối tượng mới với các bản sao của các thuộc tính cấp cao nhất của đối tượng gốc. Đối với các thuộc tính là kiểu nguyên thủy (ví dụ: số, chuỗi, boolean), giá trị của nó sẽ được sao chép. Tuy nhiên, đối với các thuộc tính là đối tượng (như mảng hoặc đối tượng lồng nhau), chỉ có tham chiếu được sao chép — chứ không phải dữ liệu thực tế.
Điều này có nghĩa là trong khi đối tượng mới có bản sao riêng của các thuộc tính cấp cao nhất thì các đối tượng hoặc mảng lồng nhau vẫn được chia sẻ giữa bản gốc và bản sao.
VD:
const original = { name: "Alice", details: { age: 25, city: "Wonderland" }
}; // Shallow copy
const shallowCopy = { ...original }; // Modify the nested object in the shallow copy
shallowCopy.details.city = "Looking Glass"; // Original object is also affected
console.log(original.details.city); // Output: "Looking Glass"
Cách tạo một bản Shallow Copy
1. Sử dụng toán tử spread (...):
const shallowCopy = { ...originalObject };
2. Sử dụng Object.assign():
const shallowCopy = Object.assign({}, originalObject);
Mặc dù các phương pháp này nhanh chóng và dễ dàng, nhưng chúng không phù hợp với các đối tượng lồng nhau sâu.
Deep copy là gì?
Một bản Deep Copy sẽ sao chép mọi thuộc tính và thuộc tính con của đối tượng gốc. Điều này đảm bảo rằng bản sao hoàn toàn độc lập với bản gốc và các thay đổi đối với bản sao không ảnh hưởng đến đối tượng gốc.
Deep copy là điều cần thiết khi xử lý các cấu trúc dữ liệu phức tạp như đối tượng hoặc mảng lồng nhau, đặc biệt là trong các trường hợp mà tính toàn vẹn dữ liệu là rất quan trọng.
VD:
const original = { name: "Alice", details: { age: 25, city: "Wonderland" }
}; // Deep copy using JSON methods
const deepCopy = JSON.parse(JSON.stringify(original)); // Modify the nested object in the deep copy
deepCopy.details.city = "Looking Glass"; // Original object remains unchanged
console.log(original.details.city); // Output: "Wonderland"
Cách tạo một bản Deep Copy
1. Sử dụng JSON.stringify() và JSON.parse():
Chuyển đổi đối tượng thành một chuỗi JSON và sau đó phân tích cú pháp nó trở lại thành một đối tượng mới. Tuy nhiên, phương pháp này có những hạn chế: không thể xử lý các tham chiếu vòng và bỏ qua các thuộc tính như hàm, undefined hoặc Symbol.
const deepCopy = JSON.parse(JSON.stringify(originalObject));
2. Sử dụng Thư viện:
Các thư viện như Lodash cung cấp các phương thức sao chép sâu mạnh mẽ.
const _ = require('lodash'); const deepCopy = _.cloneDeep(originalObject);
3. Hàm đệ quy tùy chỉnh:
Để kiểm soát hoàn toàn, bạn có thể viết một hàm đệ quy để sao chép các đối tượng lồng nhau.
So sánh Shallow Copy và Deep Copy
Khi nào nên sử dụng Shallow Copy?
- Đối tượng phẳng: Khi xử lý các đối tượng đơn giản không có thuộc tính lồng nhau.
- Hiệu suất: Khi tốc độ là rất quan trọng và bạn không cần xử lý dữ liệu lồng nhau sâu.
- Thay đổi tạm thời: Khi bạn định sửa đổi các thuộc tính cấp cao nhất nhưng chia sẻ dữ liệu lồng nhau.
VD: Sao chép đối tượng cài đặt của người dùng để thực hiện các điều chỉnh nhanh chóng:
const userSettings = { theme: "dark", layout: "grid" };
const updatedSettings = { ...userSettings, layout: "list" };
Khi nào nên sử dụng Deep Copy?
- Cấu trúc phức tạp: Đối với các đối tượng có nhiều cấp độ lồng nhau.
- Tránh tác dụng phụ: Khi bạn cần đảm bảo rằng các thay đổi trong bản sao không ảnh hưởng đến bản gốc.
- Quản lý trạng thái: Trong các framework như React hoặc Redux, nơi tính bất biến là rất quan trọng.
VD: Nhân đôi trạng thái của trò chơi hoặc ứng dụng:
const gameState = { level: 5, inventory: { weapons: ["sword", "shield"], potions: 3 }
}; // Deep copy ensures no side effects
const savedState = JSON.parse(JSON.stringify(gameState));
Những sai lầm và cạm bẫy thường gặp
- Cho rằng Shallow Copy luôn đủ: Các nhà phát triển thường sử dụng sai các phương pháp shallow copy cho các đối tượng lồng nhau, dẫn đến những thay đổi không mong muốn trong dữ liệu gốc.
- Lạm dụng phương thức JSON: Mặc dù JSON.stringify/JSON.parse đơn giản, nhưng nó không hoạt động cho tất cả các đối tượng (ví dụ: những đối tượng chứa phương thức hoặc tham chiếu vòng).
- Bỏ qua hiệu suất: Các phương thức deep copy có thể chậm hơn, đặc biệt là đối với các đối tượng lớn, vì vậy hãy sử dụng chúng một cách thận trọng.
Kết luận
Hiểu được sự khác biệt giữa shallow copy và deep copy là điều cần thiết để viết mã JavaScript không có lỗi. Shallow copy hiệu quả cho các cấu trúc phẳng, trong khi deep copy là không thể thiếu đối với các đối tượng phức tạp, lồng nhau. Hãy chọn phương pháp phù hợp dựa trên cấu trúc dữ liệu và nhu cầu ứng dụng của bạn và tránh các cạm bẫy tiềm ẩn bằng cách biết các hạn chế của từng phương pháp.