Cách tạo bản sao sâu của đối tượng trong JavaScript
JavaScript là một ngôn ngữ lập trình mạnh mẽ nhưng cũng có những điểm phức tạp mà các lập trình viên cần nắm vững. Một trong số đó là cách tạo bản sao sâu của các đối tượng. Trong bài viết này, chúng ta sẽ đi sâu vào cách thực hiện điều này và các khái niệm liên quan.
Mục tiêu của bài viết
Bài viết này sẽ giúp bạn:
- Phân biệt giữa kiểu dữ liệu nguyên thuỷ và tham chiếu
- Sử dụng
typeofvàinstanceofmột cách chính xác - Hiểu cách lấy kiểu dữ liệu một cách chính xác
- Duyệt qua
MapvàSet - Sử dụng
WeakMap - Hiểu chuỗi prototype
- Phân biệt giữa
Reflect.ownKeys()vàhasOwnProperty - Phân biệt giữa
Reflect.ownKeys()vàObject.keys - Phân biệt giữa
Reflect.ownKeys()và vòng lặpfor...in
Cách hoạt động của hàm tùy chỉnh
Hàm tạo bản sao sâu sẽ xử lý các trường hợp sau:
- Giá trị nguyên thuỷ: Trả về trực tiếp các giá trị như chuỗi, số, boolean...
- Tham chiếu vòng: Sử dụng
WeakMapđể theo dõi các đối tượng đã sao chép, tránh đệ quy vô hạn khi một đối tượng tham chiếu đến chính nó. - Các kiểu đặc biệt:
- Date: sao chép bằng
new Date(original); - RegExp: sao chép bằng
new RegExp(original); - Map/Set: tạo các thể hiện và sao chép đệ quy giá trị của chúng;
- Date: sao chép bằng
- Mảng/Đối tượng: tạo các thể hiện mới và sao chép đệ quy các thuộc tính lồng nhau.
Ví dụ mã code
Dưới đây là một ví dụ về cách tạo hàm sao chép sâu:
javascript
function deepClone(original, cloned = new WeakMap()) {
// 1. Xử lý các giá trị nguyên thuỷ (sao chép trực tiếp)
if (original === null || typeof original !== "object") {
return original;
}
// 2. Xử lý tham chiếu vòng (tránh đệ quy vô hạn)
if (cloned.has(original)) {
return cloned.get(original);
}
// 3. Xử lý các loại đối tượng đặc biệt
const type = Object.prototype.toString.call(original);
// Sao chép Date
if (type === "[object Date]") {
const clone = new Date(original);
cloned.set(original, clone);
return clone;
}
// Sao chép RegExp
if (type === "[object RegExp]") {
const clone = new RegExp(original.source, original.flags);
clone.lastIndex = original.lastIndex; // Bảo tồn lastIndex
cloned.set(original, clone);
return clone;
}
// Sao chép Map
if (type === "[object Map]") {
const clone = new Map();
cloned.set(original, clone); // Lưu trữ trước khi đệ quy để xử lý tham chiếu vòng
original.forEach((value, key) => {
clone.set(deepClone(key, cloned), deepClone(value, cloned));
});
return clone;
}
// Sao chép Set
if (type === "[object Set]") {
const clone = new Set();
cloned.set(original, clone);
original.forEach(value => {
clone.add(deepClone(value, cloned));
});
return clone;
}
// 4. Sao chép mảng hoặc đối tượng thông thường
const clone = Array.isArray(original) ? [] : {};
cloned.set(original, clone); // Lưu trữ để xử lý tham chiếu vòng
// Sao chép đệ quy các thuộc tính lồng nhau
Reflect.ownKeys(original).forEach(key => {
clone[key] = deepClone(original[key], cloned);
});
return clone;
}
// Ví dụ kiểm tra
const original = {
name: "Bob",
birth: new Date("1990-01-01"),
regex: /hello/g,
map: new Map([["a", 1]]),
set: new Set([1, 2, 3]),
nested: { x: 10, y: [20, 30] }
};
// Thêm tham chiếu vòng (original tham chiếu đến chính nó)
original.self = original;
const cloned = deepClone(original);
// Kiểm tra sự độc lập
cloned.nested.x = 100;
cloned.map.set("b", 2);
console.log(original.nested.x); // 10 (original không thay đổi)
console.log(cloned.nested.x); // 100 (clone đã thay đổi)
console.log(cloned.self === cloned); // true (tham chiếu vòng được bảo tồn)
Thực hành tốt nhất
- Luôn kiểm tra tham chiếu vòng trước khi sao chép.
- Sử dụng
WeakMapđể quản lý các đối tượng đã sao chép. - Kiểm tra các loại đối tượng đặc biệt để đảm bảo sao chép chính xác.
Những cạm bẫy thường gặp
- Không xử lý các tham chiếu vòng có thể dẫn đến lỗi đệ quy.
- Bỏ qua các kiểu đối tượng đặc biệt có thể dẫn đến sao chép không chính xác.
Mẹo hiệu suất
- Tránh sao chép các đối tượng lớn nếu không cần thiết.
- Tối ưu hóa việc kiểm tra kiểu và cấu trúc dữ liệu để giảm thiểu thời gian thực thi.
Giải quyết sự cố
Nếu bạn gặp lỗi hoặc hành vi không mong muốn, hãy kiểm tra:
- Các tham chiếu vòng trong dữ liệu gốc.
- Các kiểu dữ liệu đặc biệt mà bạn có thể đã bỏ qua.
Kết luận
Việc tạo bản sao sâu trong JavaScript không chỉ giúp bạn bảo vệ dữ liệu gốc mà còn giúp bạn thao tác linh hoạt hơn với các đối tượng phức tạp. Hãy thử nghiệm với mã code trên và áp dụng vào các dự án của bạn. Nếu bạn có câu hỏi hoặc muốn chia sẻ kinh nghiệm của mình, hãy để lại bình luận bên dưới!
Câu hỏi thường gặp (FAQ)
1. Bản sao sâu là gì?
Bản sao sâu là một bản sao hoàn toàn tách biệt của một đối tượng, bao gồm cả các đối tượng lồng nhau.
2. Sự khác biệt giữa bản sao nông và bản sao sâu là gì?
Bản sao nông chỉ sao chép tham chiếu của đối tượng, trong khi bản sao sâu sao chép hoàn toàn các thuộc tính và đối tượng con.
3. Khi nào thì nên sử dụng bản sao sâu?
Khi bạn cần thay đổi dữ liệu mà không làm ảnh hưởng đến dữ liệu gốc.