JavaScript Guidebook

JavaScript 完全知识体系

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。Reflect 不是一个函数对象,因此它不是不可构造的。

设计目的:

  1. 将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。

  2. 修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc) 则会返回 false

  3. 让 Object 的 命令式操作 都变成 函数行为。比如 name in objdelete obj[name],而 Relfect.has(obj, name)Reflect.deleteProperty(obj, name) 让它们变成了函数行为

  4. Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为。

Proxy(target, {
set: function(target, name, value, receiver) {
const success = Reflect.set(target, name, value, receiver);
if (success) {
console.log('property ' + name + ' on ' + target + ' set to ' + value);
}
return successs;
},
});

上面代码中,Proxy 方法拦截 target 对象的属性赋值行为。它采用 Reflect.set 方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。

const proxy = new Proxy(obj, {
get(target, name) {
console.log('get', target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log('has' + name);
return Reflect.has(target, name);
},
});

上面代码中,每一个 Proxy 对象的拦截操作(getdeletehas),内部都调用对应的 Reflect 方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。

有了 Reflect 对象以后,很多操作会更易读。

// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
// 1
// 新写法
Reflect.apply(Math.floor, undefined, [1.75]);
// 1

与大多数全局对象不同,Reflect 没有构造函数,你不能将其与一个 new 运算符一起使用,或者 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。

静态方法

静态方法说明
Reflect.apply对函数进行调用操作,同时传入一个数组作为调用参数,与 Function.prototype.apply 功能类似
Reflect.construct对构造函数进行 new 操作,相当于执行 new target(...args)
Reflect.definePropertyObject.defineProperty 类似
Reflect.deleteProperty作为函数的 delete 操作符,相当于执行 delete target[name]
Reflect.get获取对象属性值
Reflect.getOwnPropertyDescriptor类似于 Object.getOwnPropertyDescriptor
Reflect.getPrototypeOf类似于 Object.getPrototypeOf
Reflect.has判断对象是否存在某个属性,和 in 运算符的功能完全相同
Reflect.isExtensible类似于 Object.isExtensible
Reflect.ownKeys返回一个包含所有自身属性(不包含继承属性)的数组
Reflect.preventExtensions类似于 Object.preventExtensions
Reflect.set将值分配给属性的函数,返回 Boolean,如果成功,则返回 true
Reflect.setPrototypeOf类似于 Object.setPrototyeOf

与传统方法的对比优势

Reflect 操作对象更加符合面向对象,操作对象的方法全部都挂在 Reflect。

Reflect 操作对象老方法操作对象
面向对象全部挂在 Reflect 对象上,更加符合面向对象各种指令方法,=indelete
函数式所有方法都是函数命令式、赋值、函数混用
规范报错defineProperty 无效返回 false,后面几个方法参数非法报错defineProperty 无效报错,后面几个方法参数非法不报错
方法扩展参数 receiver 指定 this 指向不能

示例

观察者模式

观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。

const person = observerable({
name: 'Zhange San',
age: 47,
});
function print() {
console.log(`${person.name}, ${person.age}`);
}
observe(print);
person.name = 'Li Si';
// Li Si, 47

上面代码中,数据对象 person 是观察目标,函数 print 是观察者。一旦数据对象发生变化,print 就会自动执行。

下面,使用 Proxy 写一个观察者模式的最简单实现,即实现 observeableobserve 这两个函数。思路是 observable 函数返回一个原始对象的 Proxy 对象,拦截赋值操作,触发充当观察者的各个函数。

const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, { set });
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}

获取设置反射属性

const Ironman = {
firstName: 'Tony',
lastName: 'Stark',
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
};
// 获取自身属性,新老方法都可以实现
Reflect.get(Ironman, 'firstName');
// Tony
Reflect.get(Ironman, 'lastName');
// Tony
Reflect.get(Ironman, 'fullName');
// Tony Stark
const Spiderman = {
firstName: 'Peter',
lastName: 'Parker',
};
// 获取反射属性,只有 Reflect 可以实现
Reflect.get(Ironman, 'fullName', Spiderman);
// Peter Parker