在上一篇文章讲到了 Proxy 类的使用,那么在本篇内容中将会讲解 Reflect 对象,Proxy 可以捕获13种不同的基本操作,这些基本操作有各自不同的反射 Reflect API 方法、参数、关联 ECMAScript 操作和不变式。
Reflect 对象是一个平凡对象,例如 Math,不像其他内置原生值一样是函数或者构造器,它特有对应于各自可控的元数据任务的静态函数。这些函数一对一对应着代理可以定义的处理函数方法,具体如下图所示:
一般来说这些方法和 Object.* 对应的方法行为类似,但是有一个区别是如果第一个参数(目标对象)不是对象的话,它会试图把他类型转换为一个对象,而这种情况下 Reflect.* 方法会抛出一个错误。
Reflect出现的背景
在前面的内容中讲到了 Proxy 的使用,就在来回顾一下,如下代码所示:
const moment = {
address: "广州",
};
const proxy = new Proxy(moment, {
get: function (target, key) {
console.log(target === moment); // true
return target[key];
},
});
console.log(proxy.address); // 广州
在上面的代码中,使用 Proxy 对接收到的 target 进行操作,实际上还是操控的是原来的 moment 对象,那么 Reflect 就很好的解决了这个问题,按照个人理解,Proxy 结合 Reflect 的流程图如下图所示:
在下文会讲到为什么会是这个流程图。
Reflect的基本使用
Reflect 的方法有很多,这里就不一一列举了,讲两三个就好了。
get()
get(...) 捕获器会在获取属性值的操作中被调用,对应的 Reflect 方法为 Reflect.get(),该方法接收三个参数,它们分别是:
target: 目标对象;property: 引用的目标对象上的字符串属性;receiver: 代理对象或继承代理对象的对象;
具体实例代码如下所示:
const moment = {
address: "广州",
};
const proxy = new Proxy(moment, {
get: function (target, key) {
return Reflect.get(target, key);
},
});
console.log(proxy.address); // 广州
set()
set() 捕获器会在设置属性值的操作中被调用,对应的 Reflect 方法为 Reflect.set(),它接收四个参数,它们分别是:
target: 目标对象;property: 引用的目标对象上的字符串属性;value: 要赋给属性的值;receiver: 代理对象或继承代理对象的对象;
具体实例代码如下所示:
const moment = {
address: "广州",
};
const proxy = new Proxy(moment, {
get: function (target, key) {
return Reflect.get(target, key);
},
set: function (target, key, value) {
const result = Reflect.set(target, key, value);
console.log(result); // true
},
});
proxy.address = "肇庆";
如果返回 true 则表示成功,返回 false 则表示失败,阳模式下回抛出 TypeError。
receiver的妙用
在前面的内容中,我们可以看出,使用 Reflect.set() 方法中,它会有一个返回值,成功或者失败,而使用 Proxy 则并没有,这也就导致了我们无法得知是否修改成功了,它不仅有这个作用,还有一个很重要的作用,那就是 get() 和 set() 方法中的最后一个参数了。
先来举一个没有使用 receiver 的例子,如下代码所示:
let index = 0;
const moment = {
_nickname: "wao",
set nickname(value) {
this._nickname = value;
},
get nickname() {
console.log(++index, "moment对象"); // 1 proxy
return this._nickname;
},
};
const proxy = new Proxy(moment, {
get: function (target, key) {
console.log(++index, "proxy"); // 2 moment对象
return Reflect.get(target, key);
},
set: function (target, key, value) {
Reflect.set(target, key, value);
},
});
console.log(proxy.nickname);
这段代码的主要流程主要是经过了以下的步骤,具体如下图所示:
这些代码经过了这些步骤之后,最终把 wao 值返回,通过查看 index 的输出值也可以查看证实这一论证。在这个时候,我们这个时候的 this 指向的 moment 对象,它已经绕过了 Proxy 直接访问原对象了。
我们要想改变它的 this 指向,那么我们可以使用最后一个参数 receiver,我们再来看看这个参数是什么来的,如下图所示:
这个 receiver 参数正是发挥 Reflect 对象的关键作用了,当我们传这个参数到 Reflect 的时候,moment 对象的 this 也从 moment 改变成了 proxy 了:
修改一下代码,你会发现,proxy 对象中的 get() 方法被调用了两次:
const moment = {
_nickname: "wao",
get nickname() {
return this._nickname;
},
};
const proxy = new Proxy(moment, {
get: function (target, key, receiver) {
console.log(`访问了 ${key} 属性`);
return Reflect.get(target, key, receiver);
},
});
proxy.nickname;
具体的输出如下图所示:
这也验证了我们内容开头中所讲到的流程图。
construct()
construct() 捕获器会在 new 操作符中被嗲欧勇,对应的 Reflect 方法为 Reflect.construct(),该方法接收三个参数,它们分别是:
taret: 目标构造函数;argumentList: 传给目标构造函数的参数列表;newTarget:最初被调用的构造函数;
具体实例代码如下所示:
function moment() {}
const proxy = new Proxy(moment, {
construct: function (target, args, newTarget) {
console.log(target); // [Function: moment]
console.log(args); // 7
console.log(newTarget); // [Function: moment]
return Reflect.construct(...arguments);
},
});
new proxy(7);
数据绑定和可观察对象
通过 Proxy 可以把运行时钟原本不相关的部分联系到一起,这样就可以实现各种模式,从而让不同的代码相互先操作,例如可以把集合绑定到一个事件派发程序,每次插入新实例时都会发送消息:
const UserList = [];
function emit(value) {
console.log(value);
}
const proxy = new Proxy(UserList, {
set(target, key, value, receive) {
const result = Reflect.set(...arguments);
if (result) emit(Reflect.get(target, key, receive));
return result;
},
});
proxy.push("moment"); // moment
proxy.push("nickname"); // nickname
最后完结撒花,Reflect 的元数据能力提供了模拟各种语法特性的编程等价物,把之前隐藏的抽象数据暴露出来。
解决 Proxy 无法解决的问题
在上一篇文章中,我们的 Proxy 对象存在一些问题,这个时候我们的 Reflect 就可以对这些问题很好地解决了:
const target = new Date();
const proxy = new Proxy(target, {});
console.log(proxy instanceof Date); // true
proxy.getDate(); // TypeError: this is not a Date object.
// 加入了 Reflect 之后
const target = new Date();
const proxy = new Proxy(target, {
get(target, prop, receiver) {
if (typeof target[prop] === 'function') {
return function(...args) {
return target[prop].apply(target, args);
};
}
return Reflect.get(target, prop, receiver);
}
});
console.log(proxy instanceof Date); // true
console.log(proxy.getDate()); // 正常输出当前日期的日部分,不会抛出错误
参考文献
JavaScript高级程序设计;
总结
这篇文章讲完,将会讲解 Typescript 中的 Reflect Metadata。
你要忍,忍到春暖花开;你要走,走到灯火通明;你要看过世界辽阔,再评判是好是坏,你要卯足劲变好,再旗鼓相当站在不敢想象的人身边; 你要变成想象中的样子,这件事,一步都不能让。
本文正在参加「金石计划」