Sử dụng structuredClone để thực hiện Deep Clone trong JavaScript

Deep clone (sao chép sâu) là một bài toán kinh điển trong lập trình JavaScript. Trước đây, lập trình viên thường giải quyết vấn đề này thông qua đệ quy thủ công hoặc sử dụng JSON.parse(JSON.stringify(obj)). Tuy nhiên, từ năm 2022, trình duyệt đã cung cấp một hàm toàn cục mạnh mẽ hơn là structuredClone().

structuredClone() cho phép tạo ra một bản sao sâu của các đối tượng JavaScript mà không gặp phải các hạn chế về cấu trúc phức tạp hay tham chiếu vòng (circular references) như phương pháp dùng JSON.

So sánh các phương pháp sao chép

1. Shallow Copy với Spread Operator

Toán tử ... chỉ thực hiện sao chép nông (shallow copy). Điều này có nghĩa là các đối tượng lồng nhau vẫn sẽ tham chiếu đến cùng một địa chỉ vùng nhớ.

const product = {
  id: 101,
  info: { price: 500 }
};

const shallow = { ...product };
shallow.info.price = 750;

console.log(product.info.price); // Kết quả: 750
console.log(shallow.info.price); // Kết quả: 750

2. Deep Clone bằng JSON

Đây là cách phổ biến nhưng tồn tại nhiều nhược điểm như: mất dữ liệu kiểu Date, RegExp, Map, Set và không xử lý được tham chiếu vòng.

const user = {
  name: "Hoàng",
  meta: { rank: 1 }
};

const jsonClone = JSON.parse(JSON.stringify(user));
jsonClone.meta.rank = 2;

console.log(user.meta.rank); // Kết quả: 1 (An toàn)
console.log(jsonClone.meta.rank); // Kết quả: 2

3. Sử dụng structuredClone()

Phương thức này giải quyết triệt để vấn đề của hai cách trên, hỗ trợ tốt các kiểu dữ liệu phức tạp.

const config = {
  theme: "dark",
  layers: { count: 5 }
};

const deepCopy = structuredClone(config);
deepCopy.layers.count = 10;

console.log(config.layers.count); // Kết quả: 5
console.log(deepCopy.layers.count); // Kết quả: 10

Xử lý tham chiếu vòng (Circular References)

Một trong những điểm mạnh nhất của structuredClone() là khả năng xử lý các đối tượng tự tham chiếu đến chính nó, điều mà JSON không thể làm được.

const nodeA = { name: "Node A" };
const nodeB = { name: "Node B" };
nodeA.child = nodeB;
nodeB.parent = nodeA; // Tạo tham chiếu vòng

try {
  const jsonFail = JSON.stringify(nodeA);
} catch (e) {
  console.log("JSON thất bại!"); // TypeError: Converting circular structure to JSON
}

const safeClone = structuredClone(nodeA);
console.log(safeClone.child.parent === safeClone); // true

Xử lý giá trị undefined và Function

Mặc dù mạnh mẽ, structuredClone() vẫn có những quy tắc riêng đối với các kiểu dữ liệu không thể tuần tự hóa:

  • undefined: Được giữ nguyên giá trị (JSON sẽ xóa bỏ thuộc tính nếu giá trị là undefined).
  • Function: Không thể sao chép. Nếu đối tượng chứa hàm, structuredClone() sẽ ném ra lỗi DataCloneError.
const data = {
  value: undefined,
  process: () => console.log("running")
};

// structuredClone(data); // Sẽ ném lỗi vì có chứa function

Chế độ Transferable Objects

structuredClone() hỗ trợ tham số thứ hai cho phép "chuyển nhượng" (transfer) các đối tượng thay vì sao chép. Khi một đối tượng được chuyển nhượng, nó sẽ không còn khả dụng ở đối tượng gốc.

const buffer = new Uint8Array(1024 * 1024); // 1MB dữ liệu
for (let i = 0; i < buffer.length; i++) buffer[i] = i;

const movedData = structuredClone(buffer, {
  transfer: [buffer.buffer]
});

console.log(buffer.byteLength); // Kết quả: 0 (Dữ liệu đã bị chuyển đi)
console.log(movedData.byteLength); // Kết quả: 1048576

Cơ chế hoạt động và Hạn chế

structuredClone() sử dụng thuật toán Structured Clone Algorithm. Đây là thuật toán được sử dụng để truyền dữ liệu giữa các Web Workers (postMessage) hoặc lưu trữ trong IndexedDB.

Những thứ không thể sao chép:

  • Hàm (Function): Sẽ gây ra ngoại lệ DataCloneError.
  • DOM Nodes: Không thể sao chép các thành phần HTML trực tiếp.
  • Property Descriptors: Các thiết lập như read-only, getters/setters sẽ không được bảo toàn; bản sao sẽ trở thành thuộc tính thông thường.
  • Prototype Chain: Chuỗi prototype sẽ không được sao chép. Đối tượng mới sẽ là instance của Object hoặc kiểu dữ liệu tương ứng, không giữ được quan hệ kế thừa tùy chỉnh.

Hiệu suất

Đối với các tập dữ liệu lớn và phức tạp, structuredClone() thường có hiệu suất tốt hơn và đáng tin cậy hơn so với việc lạm dụng JSON.stringify. Nó tránh được bước trung gian chuyển đổi sang chuỗi và giải mã ngược lại, đồng thời tối ưu hóa việc xử lý các kiểu dữ liệu nhị phân thông qua cơ chế chuyển nhượng (transfer).

Thẻ: JavaScript web-api deep-clone frontend-development

Đăng vào ngày 14 tháng 9 lúc 08:40