0
0
Lập trình
Admin Team
Admin Teamtechmely

Cách tạo bản sao sâu của đối tượng trong JavaScript

Đăng vào 7 tháng trước

• 5 phút đọc

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:

  1. Phân biệt giữa kiểu dữ liệu nguyên thuỷ và tham chiếu
  2. Sử dụng typeofinstanceof một cách chính xác
  3. Hiểu cách lấy kiểu dữ liệu một cách chính xác
  4. Duyệt qua MapSet
  5. Sử dụng WeakMap
  6. Hiểu chuỗi prototype
  7. Phân biệt giữa Reflect.ownKeys()hasOwnProperty
  8. Phân biệt giữa Reflect.ownKeys()Object.keys
  9. Phân biệt giữa Reflect.ownKeys() và vòng lặp for...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:

  1. Giá trị nguyên thuỷ: Trả về trực tiếp các giá trị như chuỗi, số, boolean...
  2. 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ó.
  3. 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;
  4. 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 Copy
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.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào