Meta programming
Các object Proxy và Reflect cho phép bạn chặn và định nghĩa hành vi tùy chỉnh cho các phép toán ngôn ngữ cơ bản (ví dụ: tra cứu thuộc tính, gán giá trị, liệt kê, gọi hàm, v.v.). Với sự hỗ trợ của hai object này, bạn có thể lập trình ở cấp độ meta của JavaScript.
Proxy
Các object Proxy cho phép bạn chặn một số phép toán nhất định và triển khai hành vi tùy chỉnh.
Ví dụ, lấy một thuộc tính trên một object:
const handler = {
get(target, name) {
return name in target ? target[name] : 42;
},
};
const p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42
Object Proxy định nghĩa một target (một object rỗng ở đây) và một object handler, trong đó một trap get được triển khai. Ở đây, một object được proxy sẽ không trả về undefined khi lấy các thuộc tính không tồn tại, mà thay vào đó trả về số 42.
Có thêm các ví dụ trên trang tham chiếu Proxy.
Thuật ngữ
Các thuật ngữ sau đây được sử dụng khi nói về chức năng của proxy.
- handler
-
Object giữ chỗ chứa các trap.
- trap
-
Các phương thức cung cấp quyền truy cập thuộc tính. (Điều này tương tự như khái niệm trap trong hệ điều hành.)
- target
-
Object mà proxy ảo hóa. Nó thường được sử dụng như bộ nhớ nền cho proxy. Các bất biến (invariant - ngữ nghĩa không thay đổi) liên quan đến tính không thể mở rộng của object hoặc các thuộc tính không thể cấu hình được kiểm tra đối với target.
- invariant
-
Ngữ nghĩa không thay đổi khi triển khai các phép toán tùy chỉnh được gọi là invariant. Nếu bạn vi phạm các invariant của một handler, một
TypeErrorsẽ được ném ra.
Handler và trap
Bảng sau đây tóm tắt các trap có sẵn cho các object Proxy. Xem các trang tham chiếu để biết giải thích chi tiết và ví dụ.
Proxy có thể thu hồi
Phương thức Proxy.revocable() được dùng để tạo một object Proxy có thể thu hồi. Điều này có nghĩa là proxy có thể bị thu hồi thông qua hàm revoke và tắt proxy đó.
Sau đó, bất kỳ phép toán nào trên proxy sẽ dẫn đến TypeError.
const revocable = Proxy.revocable(
{},
{
get(target, name) {
return `[[${name}]]`;
},
},
);
const proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.foo = 1; // TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.foo; // TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
console.log(typeof proxy); // "object", typeof doesn't trigger any trap
Reflection
Reflect là một object tích hợp sẵn cung cấp các phương thức cho các phép toán JavaScript có thể bị chặn. Các phương thức này giống với các phương thức của proxy handler.
Reflect không phải là một function object.
Reflect giúp chuyển tiếp các phép toán mặc định từ handler đến target.
Với Reflect.has() chẳng hạn, bạn có thể sử dụng toán tử in như một hàm:
Reflect.has(Object, "assign"); // true
Hàm apply() tốt hơn
Trước khi có Reflect, bạn thường sử dụng phương thức Function.prototype.apply() để gọi một hàm với giá trị this và arguments được cung cấp dưới dạng mảng (hoặc một object dạng mảng).
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
Với Reflect.apply điều này trở nên ít dài dòng hơn và dễ hiểu hơn:
Reflect.apply(Math.floor, undefined, [1.75]);
// 1
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);
// "hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index;
// 4
Reflect.apply("".charAt, "ponies", [3]);
// "i"
Kiểm tra xem việc định nghĩa thuộc tính có thành công không
Với Object.defineProperty, trả về một object nếu thành công hoặc ném TypeError nếu thất bại, bạn sẽ dùng khối try...catch để bắt bất kỳ lỗi nào xảy ra khi định nghĩa thuộc tính. Vì Reflect.defineProperty() trả về trạng thái thành công dạng Boolean, bạn có thể chỉ cần dùng khối if...else ở đây:
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}