面试官: 工作5年了 不知道Reflect嘛?
作者:某某某人
出处:juejin.cn/post/7419978042246365221
先有问题再有答案
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 代表的是代理对象本身或者继承自代理对象的对象,它表示触发陷阱时正确的上下文
。