先有问题再有答案
- Reflect是什么
- Reflect都有哪些方法
- 这些方法存在的意义是什么
- Reflect的方法为什么不放在Object上
- Reflect的设计目的是什么
- 为什么proxy里一定要使用reflect
Reflect是什么
在 JavaScript 中,Reflect 是一个内置的全局对象,对一些函数式的操作提供了面向对象的 API。 Reflect 不是一个函数对象,因此它是不可构造的。它所有的方法都是静态的,类似于 Math 对象。
Reflect方法
目前共13个静态方法 可以分为函数相关,原型相关,对象相关三大类。
1、函数相关
Reflect.apply方法用于绑定this对象后执行给定函数。 一般来说,如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args),采用Reflect对象可以简化这种操作。
Reflect.construct方法等同于new target(…args),这提供了一种不使用new,来调用构造函数的方法。
Reflect.getPrototypeOf 等同于 Object.getPrototypeOf(),用于获取对象的原型(即内部[[Prototype]]属性的值)。
Reflect.setPrototypeOf 基本等同于 Object.setPrototypeOf(),用于设置对象的原型(即内部[[Prototype]]属性的值)。
2、对象相关
- Reflect.defineProperty() 方法基本等同于 Object.defineProperty,但返回值略有不同。如果定义属性成功,它会返回true,否则返回false。
- Reflect.deleteProperty() 方法基本等同于 delete operator,用于删除一个对象的属性。
- Reflect.get(target, propertyKey, receiver) 方法用于读取属性值,等同于 target[propertyKey],但receiver参数可以改变getter的this对象。
- Reflect.set(target, propertyKey, value, receiver) 方法用于设置属性值,等同于target[propertyKey] = value,但receiver参数可以改变setter的this对象。
- Reflect.has(target, propertyKey) 方法基本等同于 propertyKey in target,用于检查一个属性是否在某个对象中。
- Reflect.getOwnPropertyDescriptor(target, propertyKey) 方法用于获取对象自身的某个属性的属性描述符,等同于Object.getOwnPropertyDescriptor()。
- Reflect.ownKeys(target) 方法返回一个由目标对象自身的属性键组成的数组,等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
- Reflect.isExtensible(target) 方法用于判断一个对象是否可扩展,等同于 Object.isExtensible()。
- Reflect.preventExtensions(target) 方法基本等同于 Object.preventExtensions(),用于使一个对象变为不可扩展。如果操作成功则返回true,否则返回false。
Reflect vs Object
Reflect和Object的某些方法是相似甚至相同的,可能有的同学会问,为什么不直接把Reflect的方法放在Object上呢?何必新加一个对象?两者到底有什么区别?
- Reflect上不光有对象的相关操作方法还有函数相关的方法 这和Object本身代表的含义不符 因此不能放在Object上。同时Reflect为未来语言的扩展提供了一套可能的API,如果将来JavaScript想要添加新的底层操作对象的方法,它们可以被加入到Reflect上,而不是继续增加Object的静态方法,这样有助于保持Object构造函数的简洁性。
- 在Reflect出现之前,JavaScript操作对象的一些方法散布在Object构造函数上,比如Object.defineProperty。但是,这些方法的返回值和错误处理机制并不一致(例如,如果操作失败,一些方法会抛出错误,而其他一些方法则返回false)。Reflect提供了一套具有一致返回值的API,使得这些操作更加统一和可预测。
所以 当Reflect和Object方法能实现同样效果时 我们建议优先使用Reflect
设计目的
在有了上面的一些基本了解后 我们再来谈下Reflect的设计目的:
编程规范性
- 统一操作对象的方法:Reflect提供了一套具有一致返回值的API,使得这些操作更加统一和可预测。
- 提供未来的新操作 API:Reflect为未来语言的扩展提供了一套可能的API。
- 使某些操作更加函数式:JavaScript是一门支持函数式编程的语言,在某些场景中,我们可能更倾向于使用函数而不是命令式的操作。Reflect的方法都是函数更符合js函数式的思想。delete obj.xx, key in obj 这种代码都可以使用reflect替代。当Reflect和操作符(例如delete, in)能实现同样效果时 我们建议优先使用Reflect。
- 简化错误处理:像之前提到的,传统的对象操作方法在错误处理上不一致。Reflect提供的方法倾向于返回更简明的结果,如布尔值,这简化了错误处理和条件检测。
获取语言内部的基本操作
基本操作包括属性访问和赋值、属性删除、枚举、函数调用、对象构造等等;
在JavaScript中,使用Proxy时推荐配合Reflect的原因主要有以下几点:
1. 保持一致性
Reflect提供了一套与Proxy traps(拦截器)相对应的方法。使用Reflect可以确保代理对象的行为与原生对象的行为保持一致。例如,Reflect.get、Reflect.set等方法与Proxy中的get、set traps直接对应。
2. 处理内部方法
JavaScript对象的属性访问实际上是通过内部方法(如[[GET]]、[[SET]])来实现的。直接通过对象访问属性(如obj.prop)不会暴露这些内部方法的细节,而Reflect提供了对这些内部方法的直接访问。例如,Reflect.get实际上调用的是[[GET]]方法。
3. 控制this指向
在使用Proxy时,直接通过对象访问属性可能会导致this指向的问题。例如,在访问器属性(getter/setter)中,this指向的是原始对象而非代理对象。使用Reflect可以显式地控制this的指向。以下是一个示例:
const obj = {
a: 1,
get foo() {
return this.a;
}
};
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
}
});
console.log(proxy.foo); // 正确输出 1
javascript复制代码
在这个例子中,Reflect.get的第三个参数receiver确保了this指向代理对象proxy,而不是原始对象obj。
4. 避免重复代码
使用Reflect可以避免在Proxy traps中重复编写相同的逻辑。例如,如果你在set trap中需要设置属性值,使用Reflect.set可以简化代码:
const proxy = new Proxy(obj, {
set(target, prop, value) {
// 可以在这里添加额外的逻辑
return Reflect.set(target, prop, value);
}
});
javascript复制代码
5. 更好的错误处理
Reflect方法会返回一个布尔值,表示操作是否成功,这有助于更好地处理错误情况。例如,Reflect.set会返回true或false,表示属性设置是否成功:
const result = Reflect.set(obj, 'a', 10);
if (!result) {
console.error('属性设置失败');
}
javascript复制代码
总结
使用Reflect与Proxy配合,可以更精确地控制代理对象的行为,保持代码的一致性和可读性,同时避免因直接访问属性而引入的潜在问题。虽然不使用Reflect也能实现代理功能,但使用Reflect可以使代码更健壮、更易于维护。
看个例子,在get属性里打印的this不一致
<script>
const obj = {
a: 1,
get foo() {
console.log(this); // 打印的是proxy
return this.a;
},
say() {
console.log('obj:', this) // 打印的是proxy
}
};
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
}
});
console.log(proxy.foo);
console.log(proxy.say());
</script>
<script>
const obj2 = {
a: 1,
get foo() {
console.log(this); // 打印的是obj2
return this.a;
},
say() {
console.log('obj2:', this) // 打印的是proxy2
}
};
const proxy2 = new Proxy(obj2, {
get(target, prop, receiver) {
return target[prop];
}
})
console.log(proxy2.foo);
console.log(proxy2.say());
</script>
最后看一个非常典型的例子
// 假设我们有一个更复杂的对象,其中访问器属性依赖于 this 的正确指向:
const obj = {
a: 1,
get foo() {
return this.a;
},
set foo(value) {
this.a = value;
}
};
// 不使用 Reflect
const proxy1 = new Proxy(obj, {
get(target, prop, receiver) {
return target[prop];
},
set(target, prop, value, receiver) {
target[prop] = value;
return true;
}
});
console.log(proxy1.foo); // 输出 1
proxy1.foo = 2;
console.log(proxy1.a); // 输出 1,而不是预期的 2
// 使用 Reflect
const proxy2 = new Proxy(obj, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
return Reflect.set(target, prop, value, receiver);
}
});
console.log(proxy2.foo); // 输出 1
proxy2.foo = 2;
console.log(proxy2.a); // 输出 2,符合预期
// 在这个示例中,不使用 Reflect 的代理 proxy1 在设置属性时,this.a 仍然是原始对象 obj 的 a,导致结果不符合预期。而使用 Reflect 的代理 proxy2 则正确地更新了代理对象的属性。