零基础学习JS--基础篇--元编程

Proxy 和 Reflect 对象允许你拦截并自定义基本语言操作(例如属性查找、赋值、枚举和函数调用等)。借助这两个对象,你可以在 JavaScript 进行元级别的编程。

代理

Proxy 对象可以拦截某些操作并实现自定义行为。

例如获取一个对象上的属性:

let handler = {
  get(target, name) {
    return name in target ? target[name] : 42;
  },
};

let p = new Proxy({}, handler);
p.a = 1;

console.log(p.a, p.b); // 1, 42

Proxy 对象定义了一个 target(这里是一个空对象)和一个实现了 get 陷阱的 handler 对象。这里,代理的对象在获取未定义的属性时不会返回 undefined,而是返回 42

更多例子参见 Proxy 页面。

术语:

在讨论代理的功能时会用到以下术语:

handler:包含陷阱的占位符对象(下译作“处理器”)。

陷阱:提供属性访问的方法(这类似于操作系统中陷阱的概念)。

target:代理所虚拟化的对象(下译作“目标”)。它通常用作代理的存储后端。JavaScript 会验证与不可扩展性或不可配置属性相关的不变式。

不变式:实现自定义操作时保持不变的语义称为不变式。如果你破坏了处理器的不变式,则会引发 TypeError 异常。

处理器和陷阱

以下表格中总结了 Proxy 对象可用的陷阱。详细的解释和例子请看参考页

handler.getPrototypeOf()Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
  • getPrototypeOf 方法必须返回一个对象或 null
  • 如果 target 不可扩展,Object.getPrototypeOf(proxy) 必须返回和 Object.getPrototypeOf(target) 一样的值。
handler.setPrototypeOf()Object.setPrototypeOf()
Reflect.setPrototypeOf()
如果 target 不可扩展,参数 prototype 必须与 Object.getPrototypeOf(target) 的值相同。
handler.isExtensible()Object.isExtensible()
Reflect.isExtensible()
Object.isExtensible(proxy) 必须返回和 Object.isExtensible(target) 一样的值。
handler.preventExtensions()Object.preventExtensions()
Reflect.preventExtensions()
如果 Object.isExtensible(proxy) 值为 false,那么 Object.preventExtensions(proxy) 只可能返回 true
handler.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
  • getOwnPropertyDescriptor 必须返回对象或者 undefined
  • 如果存在一个对应于 target 的属性是不可配置的自有属性,那么该属性不能被报告为不存在的。
  • 如果存在一个对应于 target 的属性是自有属性,且该 target 不可扩展,那么该属性不能被报告为不存在的。
  • 如果并不存在一个对应于 target 的属性是自有属性,且该 target 不可扩展,那么该属性不能被报告为存在的。
  • 如果并不存在一个对应于 target 的属性是自有属性,或存在一个对应于 target 的属性是可配置的自有属性,那么它不能被报告为不可配置的。
  • Object.getOwnPropertyDescriptor(target) 的结果可以通过 Object.defineProperty 应用到 target 上,且不会抛出异常。
handler.defineProperty()Object.defineProperty()
Reflect.defineProperty()
  • 如果 target 不可扩展,那么就不能添加属性。
  • 如果并不存在一个对应于 target 的属性是不可配置的自有属性,那么就不能添加(或修改)该属性为不可配置的。
  • 如果存在一个对应于 target 的属性是可配置的,那么这个属性未必是不可配置的。
  • 如果存在一个对应于 target 的属性,那么 Object.defineProperty(target, prop, descriptor) 将不会抛出异常。
  • 在严格模式下,如果 defineProperty 处理器返回 false,则会抛出 TypeError 异常。
handler.has()

属性查询

foo in proxy

继承属性查询

foo in Object.create(proxy)
Reflect.has()

  • 如果存在一个对应于 target 的属性是不可配置的自有属性,那么该属性不能被报告为不存在的。
  • 如果存在一个对应于 target 的属性是自有属性,且 target 不可扩展,那么该属性不能被报告为不存在的。
handler.get()

属性访问

proxy[foo]
proxy.bar

继承属性访问

Object.create(proxy)[foo]
Reflect.get()

  • 如果对应于 target 的属性是不可写且不可配置的数据属性,那么该属性值必须与其相同。
  • 如果对应于 target 的属性是不可配置的访问器属性,且其 [[Get]] 属性为 undefined,那么该属性值必须为 undefined
handler.set()

属性赋值

proxy[foo] = bar
proxy.foo = bar

继承属性赋值

Object.create(proxy)[foo] = bar
Reflect.set()

  • 如果对应于 target 的属性是不可写且不可配置的数据属性,那么就不能修改该属性的值使其不同于 target 上对应属性的值。
  • 如果对应于 target 的属性是不可配置的访问器属性,且其 [[Set]] 属性为 undefined,那么就不能设置该属性的值。
  • 在严格模式下,如果 set 处理器返回 false,则会抛出 TypeError 异常。
handler.deleteProperty()

属性删除

delete proxy[foo]
delete proxy.foo
Reflect.deleteProperty()

如果存在一个对应于 target 的属性是不可配置的自有属性,那么该属性不能被删除。
handler.ownKeys()Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
  • ownKeys 的返回值是一个数组。
  • 返回值中的每个元素类型为 String 或 Symbol
  • 返回值中必须包含 target 的所有不可配置自有属性的键名。
  • 如果 target 不可扩展,那么返回值中必须有且仅有 target 的所有自有属性的键名。
handler.apply()proxy(..args)
Function.prototype.apply()
Function.prototype.call()
Reflect.apply()
不存在关于 handler.apply 方法的不变式。
handler.construct()new proxy(...args)
Reflect.construct()
返回值必须是一个 Object

可撤销的Proxy

可以用 Proxy.revocable() 方法来创建可撤销的 Proxy 对象。这意味着可以通过 revoke 函数来撤销并关闭一个代理。

此后,对代理进行的任意的操作都会导致 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` 不会触发任何陷阱

反射

Reflect 是一个内置对象,它为可拦截的 JavaScript 操作提供了方法。这些方法与代理处理器所提供的方法类似。

Reflect 并不是一个函数对象。

Reflect 将默认操作从处理器转发到 target

以 Reflect.has() 为例,你可以将 in 运算符作为函数:

Reflect.has(Object, "assign"); // true
更好的apply函数:

在不借助 Reflect 的情况下,我们通常使用 Function.prototype.apply() 方法调用一个具有给定 this 值和 arguments 数组(或类数组对象)的函数。

Function.prototype.apply.call(Math.floor, undefined, [1.75]);

借助 Reflect.apply,这些操作将变得更加简洁:

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"
检查属性是否定义成功:

使用 Object.defineProperty,如果成功则返回一个对象,否则抛出一个 TypeError,你可使用 try...catch 块来捕获定义属性时发生的任何错误。因为 Reflect.defineProperty 返回一个布尔值表示的成功状态,你可以在这里使用 if...else 块:

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

这篇文章和上一篇文章一样,都是新知识,内容不多,但难于理解,里面还有很多细节大家可以点击链接进行更详细的学习,这里不再进行详细的阐述。

附:以上内容均为个人在MDN网站上学习JS的笔记,若有侵权,将在第一时间删除,若有错误,将在第一时间修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值