先有问题再有答案
Reflect是什么
Reflect都有哪些方法
这些方法存在的意义是什么
Reflect的方法为什么不放在Object上
Reflect的设计目的是什么
为什么proxy里一定要使用reflect
Reflect是什么
在 JavaScript 中,Reflect 是一个内置的全局对象,对一些函数式的操作提供了面向对象的 API。 Reflect 不是一个函数对象,因此它是不可构造的。它所有的方法都是静态的,类似于 Math 对象。
Reflect方法
目前共13个静态方法 可以分为
函数相关
,原型相关
,对象相关
三大类。
函数相关
Reflect.apply
方法用于绑定this
对象后执行给定函数。 一般来说,如果要绑定一个函数的this
对象,可以这样写fn.apply(obj, args)
,但是如果函数定义了自己的apply
方法,就只能写成Function.prototype.apply.call(fn, obj, args)
,采用Reflect
对象可以简化这种操作。
function greet(name, age) {
console.log(`Hello, my name is ${name} and I am ${age} years old.`);
}
const args = ['John', 30];
Reflect.apply(greet, null, args); // 相当于 greet(...args)
Reflect.construct
方法等同于new target(...args)
,这提供了一种不使用new
,来调用构造函数的方法。
function Person(name, age) {
this.name = name;
this.age = age;
}
const args = ['John', 30];
const instance = Reflect.construct(Person, args); // 相当于 new Person(...args)
原型相关
Reflect.getPrototypeOf
等同于 Object.getPrototypeOf(),用于获取对象的原型(即内部[[Prototype]]属性的值)。
const obj = { x: 1 };
const proto = Reflect.getPrototypeOf(obj); // 相当于 Object.getPrototypeOf(obj)
console.log(proto === Object.prototype); // true
Reflect.setPrototypeOf
基本等同于 Object.setPrototypeOf(),用于设置对象的原型(即内部[[Prototype]]属性的值)。
const obj = { x: 1 };
Reflect.setPrototypeOf(obj, Array.prototype); // 设置 obj 的原型为 Array.prototype
console.log(obj instanceof Array); // true
对象相关
-
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提供的方法倾向于返回更简明的结果,如布尔值,这简化了错误处理和条件检测。
获取语言内部的基本操作
基本操作包括属性访问和赋值
、属性删除
、枚举
、函数调用
、对象构造
等等
例如:
function originalFunction() {
console.log(this.message);
}
const context = {
message: 'Hello, Reflect!',
};
// 假设originalFunction有一个自定义的apply方法
originalFunction.apply = function() {
console.log('Custom apply method called');
};
// 如果调用originalFunction的apply方法,会调用自定义的apply,而不是原生的Function.prototype.apply
originalFunction.apply(context); // 输出: Custom apply method called
// 使用Reflect.apply可以更简单地达到同样的效果,并且不管原Function对象有没有自定义的apply方法
Reflect.apply(originalFunction, context,[]); // 输出: Hello, Reflect!
即使在特定环境下目标函数的某些行为被覆盖或修改了 Reflect对象依然是调用语言的内部基本操作,而不受外部环境的影响。
当然我们通过其他方式也是可以做到的 只是麻烦了一点...
// 使用Function.prototype.apply.call确保调用的是原生的apply方法
Function.prototype.apply.call(originalFunction, context,[]); // 输出: Hello, Reflect!
配合proxy实现代理等元编程操作
元编程是指编写可以操作或改变其他程序的程序。元编程可以改变 JavaScript 的一些基本操作的行为。
主要与这三个对象有关。
Symbol:通过内置Symbol值复写 js语言中的基本操作
。
Reflect:可以获取语言内部的基本操作
。
Proxy:通过钩子函数 拦截&改变 js语言的基本操作
。
const obj = {};
const proxyObj = new Proxy(obj,{
get(target, property, receiver){
console.log(`get ${property}`, target[property])
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver){
console.log(`set ${property}`, value)
return Reflect.set(target, property, value, receiver);
},
deleteProperty(target, property){ // 拦截属性删除
console.log(`delete ${property}`)
return Reflect.deleteProperty(target, property);
}
})
proxyObj.name = 'test'
console.log(proxyObj.name)
delete proxyObj.name;
console.log(proxyObj.name)
// 结果
set name test
get name test
test
delete name
get name undefined
undefined
这段代码展示了如何使用Proxy对象来拦截对底层对象的操作。
通过这种方法,可以在执行实际操作之前或之后插入自定义行为。这里拦截了三种操作:属性的获取 (get)、设置 (set) 和删除 (deleteProperty)。
元编程系列文章
补充
为什么proxy里一定要使用reflect?
先看下不使用reflect的例子
const obj = {
_name: 'test',
get name(){
return this._name;
}
};
const proxyObj = new Proxy(obj,{
get(target, property, receiver){
return target[property];
},
});
const child = {
_name: 'child'
};
Reflect.setPrototypeOf(child, proxyObj);
child.name // test
proxyObj.name // test
因为代理对象的get拦截中固定返回的target[property];
target永远指向obj 所以拿到的永远是obj的_name属性值。
const obj = {
_name: 'test',
get name(){
return this._name;
}
};
const proxyObj = new Proxy(obj,{
get(target, property, receiver){
return Reflect.get(target, property, receiver)
},
});
const child = {
_name: 'child'
};
Reflect.setPrototypeOf(child, proxyObj);
child.name // child
proxyObj.name // test
当我们使用Reflect时可以正确转发运行时上下文; 其实主要就是receiver
这个参数,receiver 代表的是代理对象本身或者继承自代理对象的对象,它表示触发陷阱时正确的上下文
。