你能懂得 JS 之 Reflect 反射
上一篇文章《Proxy 代理对象使用详解即原理总结》我们详细介绍了 Proxy 代理对象的使用及其工作原理(不了解的同学可以去看看哦),今天我们来聊聊它的好兄弟 Reflect 反射。
特点
-
Reflect 是一个内置全局对象,将 Object 对象的一些属于语言内部的方法也部署到 Reflect 对象上,并且未来新方法将只部署在 Reflect 对象上。这么做的目的是为了把一些底层实现提取成一个的 API,并且高度聚合到一个对象上,让代码更清晰。
-
状态标记:很多反射方法返回布尔值,表示目的操作是否成功,而原始对象的操作是抛出错误或返回操作后的对象。
let obj = {name:'孤城浪人'};
let flag = Reflect.defineProperty(obj, 'age', {value:18});
let flag1 = Object.defineProperty(obj,'sex',{value:'男'});
console.log(flag);//true
console.log(flag1);//{name: '孤城浪人', age: 18, sex: '男'}
- 用函数代替操作符:比如用 Reflect.get()代替属性访问操作符,用 Reflect.has() 代替 in 操作符或 with() 等等。例:
let obj = {name:'孤城浪人'};
console.log(obj.name);//孤城浪人
console.log(Reflect.get(obj,'name'));//孤城浪人
console.log('name' in obj);//true
console.log(Reflect.has(obj,'name'));//true
-
Reflect 对象的静态方法有13个与 Proxy 代理可以捕获的 13 种不同的基本操作一一对应,所以无论 Proxy 怎么修改默认行为,总可以在
Reflect
上获取原始的默认行为。 -
可以用 Reflact.apply() 代替 Function.prototype.apply.call()。这种情况往往发生在
现在你明白为什么说 Proxy 和 Reflect 是好兄弟了吧。主要原因就是 Proxy 会通过指定拦截函数修改代理对象的内部方法(即默认行为),但同时我们希望在修改默认行为的时候能够调用原来的默认行为,此时就需要 Reflect 对象了。Proxy 和 Reflect 相互配合往往能出奇效。
13个静态方法
Reflect 对象一共有 13 个静态方法,它们与 Proxy 代理可以捕获 13 种不同的基本操作一一对应。
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
简单使用
示例用法
下面是一些简单用法
const obj = {
name: "孤城浪人",
test() {
return this == proxyObj;
},
};
console.log(Reflect.get(obj, "name")); // "孤城浪人"
console.log(Reflect.get(obj, "age")); // undefined
Reflect.set(obj, "name", "大帅哥^o^");
console.log(obj.name); // 大帅哥^o^
console.log(Reflect.has(obj, "age")); // false
console.log(Reflect.has(obj, "name")); // true
function Person(name) {
this.name = name;
}
const person = Reflect.construct(Person, ['张三']);
console.log(person);
console.log(Reflect.deleteProperty(obj, "age")); // true
console.log(obj); // { name: "孤城浪人",test() {return this == proxyObj;},}
console.log(Reflect.ownKeys(obj)); // => [ "name", "test","age" ]
上面这些方法如果传入的第一个参数不是对象会报错。而下面这些方法第一个参数不是对象会有自己的处理逻辑,具体请参考阮一峰老师的ECMAScript 6 入门 。
Reflect.defineProperty(obj, "age", { value: 18, configurable: true }); // 返回值 true|false
console.log(obj); // { name: "孤城浪人",age:18,test() {return this == proxyObj;},}
// 返回对象属性的描述对象
console.log(Reflect.getOwnPropertyDescriptor(obj, "name")); // {value: '大帅哥^o^', writable: true, enumerable: true, configurable: true}
console.log(Reflect.getPrototypeOf(obj) === Object.prototype); // true
Reflect.setPrototypeOf(obj, { name: "原型" });
console.log(obj.__proto__); //{ name: "原型" }
与 Proxy 代理对象配合
const obj = {
name: "孤城浪人",
age: 18,
sex: "男",
};
const ProxyObj = new Proxy(obj, {
get(target, property) {
console.log(`get触发,读取${property}属性`);
// return target[property];直接操作原始对象
return Reflect.get(target, property);
// return Reflect.get(...arguments); // 简写
},
set(target, property, value) {
console.log(`set触发,赋值${property}属性`);
// target[property] = value; 直接操作原始对象
Reflect.set(target, property, value);
// Reflect.set(...arguments); // 简写
},
});
console.log(ProxyObj.name);
ProxyObj.age = 20;
console.log(ProxyObj.age);
这里使用 Proxy 对 obj 进行了代理,指定了读取和设置拦截逻辑,在自定义逻辑中使用 Reflect 对象的 API 方法操作原始对象数据,而不是直接操作原始对象,一眼就看出对原始对象做了什么操作,代码更清晰了。
解决 Proxy 代理对象的 this 指向问题
《Proxy 代理对象使用详解即原理总结》这篇文章曾说过 Reflect 可以解决代理对象的 this 问题,下面我们就好好聊聊它。
get 的第三个参数 receiver
如果对象部署了读取函数(getter),则读取函数的 this 会自动绑定 receiver。如下例中,第一次输出 this 就是 me,第二次输出由于传入了 she 作为第三个参数 receiver,所以 this 指向 she,因此输出与第一次不同。
const me = {
name: "孤城浪人",
age: 18,
sex: "男",
get personInfo() {
return `个人信息:姓名:${this.name},年龄:${this.age},性别:${this.sex}`;
},
};
const she = {
name: "李四",
age: 20,
sex: "女",
};
console.log(Reflect.get(me, "personInfo")); // 个人信息:姓名:孤城浪人,年龄:18,性别:男
console.log(Reflect.get(me, "personInfo", she));// 个人信息:姓名:李四,年龄:20,性别:女
解决问题
我们对这篇文章《Proxy 代理对象使用详解即原理总结》抛出问题的代码做如下简单改造:
const obj = {
_name: "孤城浪人",
get name() {
console.log(this)
return this._name;
},
};
const proxyObj = new Proxy(obj, {
get(target, property, receiver) {
//return target[property];
return Reflect.get(target,property,receiver);
},
});
const obj1 = {
__proto__:proxyObj,
_name:'李四'
}
console.log(obj.name);
console.log(obj1.name);
我们知道代理对象的 get 拦截函数有第三个参数,当 Proxy 代理对象作为其他对象的原型时,receiver 会指向被继承的对象(上面的代码 receiver 就是 obj1)。再将这个receiver 作为 Reflect.get 的第三个参数,那么此时 obj 中的 get 钩子中 this 就正确的指向了 obj1,也就能正确打印obj1.name
了。
参考
非常感谢这些文章给予我的帮助。
- 了解学习 Proxy 的好朋友 - Reflect,为什么需要 Reflect
- ECMAScript 6 入门 --阮一峰
总结
Reflect 对象就是内置的全局对象,因为它的静态方法与 Proxy 的捕获器一一对应,所以它们就像是一对孪生兄弟,常常放在一起使用,并且效果还不错。
好了,Reflect 的相关内容就介绍到这了,我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎大家关注!