Sử dụng Proxy để Tạo Đối Tượng JavaScript Bất Biến
JavaScript là một ngôn ngữ lập trình mạnh mẽ, nhưng các đối tượng trong JavaScript lại có tính biến đổi.
Điều này có thể dẫn đến những lỗi không mong muốn khi có những thay đổi bất ngờ xảy ra trên các cấu trúc dữ liệu mà lẽ ra phải giữ nguyên.
👉 Vậy nếu chúng ta có thể đảm bảo tính bất biến một cách nội tại, mà không cần đến thư viện bên ngoài thì sao?
Chính lúc này, Proxy xuất hiện như một giải pháp hữu hiệu.
🔍 Tại Sao Tính Bất Biến Quan Trọng
Tính bất biến mang lại nhiều lợi ích:
- Dễ dự đoán: Dữ liệu của bạn sẽ không bị thay đổi một cách ngẫu nhiên.
- Dễ dàng gỡ lỗi: Không có những thay đổi trạng thái "ẩn".
- Phù hợp với lập trình hàm: Tương thích tốt với React, Redux, và các phương pháp quản lý trạng thái khác.
Thông thường, để có được tính bất biến, lập trình viên thường cần đến các thư viện như Immutable.js
hoặc sử dụng Object.freeze
.
Tuy nhiên, Object.freeze
chỉ hoạt động ở mức độ nông — nó chỉ ngăn cản các thay đổi ở cấp độ cao nhất, mà không bảo vệ các thuộc tính lồng nhau.
Chính vì vậy, Proxy
trở thành một lựa chọn tuyệt vời ✨.
🛠 Sử Dụng Proxy Để Đảm Bảo Tính Bất Biến
Đối tượng Proxy
cho phép chúng ta chặn các thao tác được thực hiện trên một đối tượng.
Chúng ta có thể "bẫy" các thao tác thay đổi và ngăn chặn chúng.
javascript
function immutable(obj) {
return new Proxy(obj, {
set(target, prop, value) {
throw new Error(`❌ Không thể thay đổi thuộc tính "${prop}". Đối tượng là bất biến.`);
},
deleteProperty(target, prop) {
throw new Error(`❌ Không thể xóa thuộc tính "${prop}". Đối tượng là bất biến.`);
},
defineProperty(target, prop, descriptor) {
throw new Error(`❌ Không thể định nghĩa lại thuộc tính "${prop}". Đối tượng là bất biến.`);
}
});
}
✅ Ví Dụ Thực Tế
javascript
const user = immutable({
name: "Rudy",
role: "Kỹ sư Frontend",
skills: ["Angular", "TypeScript"]
});
console.log(user.name); // Rudy
user.name = "John";
// ❌ Lỗi: Không thể thay đổi thuộc tính "name". Đối tượng là bất biến.
delete user.role;
// ❌ Lỗi: Không thể xóa thuộc tính "role". Đối tượng là bất biến.
🔁 Bất Biến Sâu
Mã ở trên chỉ hoạt động với các thuộc tính cấp cao.
Nhưng nếu chúng ta muốn các đối tượng lồng nhau cũng bất biến thì sao?
Chúng ta có thể bao bọc chúng một cách đệ quy trong một proxy:
javascript
function deepImmutable(obj) {
if (obj !== null && typeof obj === "object") {
return new Proxy(obj, {
get(target, prop) {
return deepImmutable(target[prop]);
},
set() {
throw new Error("❌ Không thể thay đổi đối tượng bất biến.");
},
deleteProperty() {
throw new Error("❌ Không thể xóa thuộc tính.");
}
});
}
return obj;
}
Sử dụng:
javascript
const config = deepImmutable({
app: {
theme: "dark",
version: "1.0"
}
});
console.log(config.app.theme); // dark
config.app.theme = "light";
// ❌ Lỗi: Không thể thay đổi đối tượng bất biến.
🚀 Khi Nào Nên Sử Dụng
- Quản lý trạng thái: Ngăn chặn thay đổi không mong muốn trong các ứng dụng sử dụng React/Redux/NgRx.
- Dữ liệu chia sẻ: Khi nhiều lập trình viên làm việc với cùng một cấu trúc, tính bất biến giúp tránh xung đột.
- API: Khi trả về các đối tượng, đảm bảo tính bất biến giúp tránh việc người tiêu dùng phá vỡ các hợp đồng dữ liệu.
⚖️ Đánh Giá Các Trade-off
- Có một chi phí hiệu năng nhẹ do việc bao bọc bằng Proxy.
- Có thể không cần thiết cho các dự án nhỏ, nơi mà kỷ luật là đủ.
- Proxy không được hỗ trợ trong các môi trường cũ (nhưng hoạt động tốt trong tất cả các trình duyệt hiện đại và Node.js).
🎯 Kết Luận
Tính bất biến là một khái niệm mạnh mẽ giúp giảm thiểu lỗi và làm cho việc lý giải mã của bạn dễ dàng hơn rất nhiều.
Với Proxy, JavaScript cung cấp cho chúng ta một cách tiếp cận nội tại để đảm bảo tính bất biến mà không cần thư viện bên thứ ba.
Lần tới khi bạn muốn các đối tượng an toàn, hãy thử bao bọc chúng bằng một Proxy — và làm cho trạng thái của bạn trở nên an toàn hơn từ thiết kế.