Meta programming

Các object ProxyReflect 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:

js
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 TypeError sẽ đượ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ụ.

Handler / trap Interceptions
handler.getPrototypeOf() Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
handler.setPrototypeOf() Object.setPrototypeOf()
Reflect.setPrototypeOf()
handler.isExtensible() Object.isExtensible()
Reflect.isExtensible()
handler.preventExtensions() Object.preventExtensions()
Reflect.preventExtensions()
handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
handler.defineProperty() Object.defineProperty()
Reflect.defineProperty()
handler.has()
Property query
foo in proxy
Inherited property query
foo in Object.create(proxy)
Reflect.has()
handler.get()
Property access
proxy[foo]
proxy.bar
Inherited property access
Object.create(proxy)[foo]
Reflect.get()
handler.set()
Property assignment
proxy[foo] = bar
proxy.foo = bar
Inherited property assignment
Object.create(proxy)[foo] = bar
Reflect.set()
handler.deleteProperty()
Property deletion
delete proxy[foo]
delete proxy.foo
Reflect.deleteProperty()
handler.ownKeys() Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
handler.apply() proxy(..args)
Function.prototype.apply() and Function.prototype.call()
Reflect.apply()
handler.construct() new proxy(...args)
Reflect.construct()

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.

js
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:

js
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ị thisarguments được cung cấp dưới dạng mảng (hoặc một object dạng mảng).

js
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:

js
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:

js
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}