Việc so sánh object trong JavaScript là một yêu cầu phổ biến mà mọi developer đều gặp phải. Khác với các kiểu dữ liệu nguyên thủy có thể so sánh trực tiếp bằng toán tử ===, object yêu cầu những phương pháp tinh tế hơn để đảm bảo tính chính xác và hiệu suất tối ưu.
Tại Sao So Sánh Object Lại Phức Tạp?
JavaScript phân biệt hai loại so sánh chính: referential equality (so sánh tham chiếu) và deep equality (so sánh sâu). Toán tử === chỉ kiểm tra xem hai object có cùng tham chiếu trong bộ nhớ hay không, không phải nội dung thực tế:
Object Đơn Giản
1.JSON.stringify()
Đối với object đơn giản không chứa function, undefined hoặc circular references, JSON.stringify() là lựa chọn hiệu quả nhất
const obj1 = { id: 1, name: 'Product A', price: 100 };
const obj2 = { id: 1, name: 'Product A', price: 100 }; console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true
Ưu điểm:
- Tốc độ xử lý nhanh
- Cú pháp đơn giản, dễ hiểu
- Phù hợp với object có cấu trúc cố định
Nhược điểm:
- Thứ tự thuộc tính ảnh hưởng kết quả
- Không xử lý được undefined, function, Symbol
- Các giá trị NaN và Infinity được chuyển thành null
2.Object Flat
Bạn có thể so sánh thủ công như sau:
function compareSimpleObjects(obj1, obj2) { const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (let key of keys1) { if (obj1[key] !== obj2[key]) return false; } return true;
}
Object Phức Tạp
Khi object trở nên phức tạp, lồng nhiều cấp hoặc chứa array, việc dùng stringify không còn hiệu quả. Lúc này, bạn cần đến so sánh đệ quy:
function deepEqual(a, b) { if (a === b) return true; if (typeof a !== "object" || typeof b !== "object" || a == null || b == null) return false; const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; for (let key of keysA) { if (!keysB.includes(key) || !deepEqual(a[key], b[key])) { return false; } } return true;
}
Ưu điểm:
- Có thể so sánh object lồng nhau, array, giá trị null.
Nhược điểm:
- Hiệu suất kém với object lớn hoặc lồng sâu.
- Không xử lý được circular reference, dẫn đến lặp vô hạn.
Tối ưu hóa đệ quy
Các kỹ thuật tối ưu được áp dụng:
- Early termination: Dừng sớm khi phát hiện khác biệt
- Circular reference handling: Sử dụng WeakMap(tương tự Map nhưng key chỉ có thể là object) để tránh vòng lặp vô hạn
- Reference comparison: Kiểm tra tham chiếu trước khi so sánh sâu
function optimizedDeepEqual(obj1, obj2, visited = new WeakMap()) { // Early termination - so sánh tham chiếu trước if (obj1 === obj2) return true; // Kiểm tra circular reference if (visited.has(obj1)) { return visited.get(obj1) === obj2; } visited.set(obj1, obj2); // Kiểm tra kiểu dữ liệu if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) { return false; } const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); // Early termination - kiểm tra số lượng thuộc tính if (keys1.length !== keys2.length) return false; // So sánh từng thuộc tính for (let key of keys1) { if (!keys2.includes(key)) return false; if (!optimizedDeepEqual(obj1[key], obj2[key], visited)) { return false; } } return true;
}
Tổng kết
Khi làm việc với object phức tạp nhiều cấp, cần chú ý:
- Tránh JSON.stringify() với object lớn vì có thể gây stack overflow
- Sử dụng thư viện chuyên dụng thay vì tự viết đệ quy cho production
- Cân nhắc shallow comparison trước khi thực hiện deep comparison
- Monitoring memory usage khi xử lý object có circular references