面试官: 工作5年了 不知道Reflect嘛?

作者:某某某人

出处:juejin.cn/post/7419978042246365221

先有问题再有答案

  1. Reflect是什么
  2. Reflect都有哪些方法
  3. 这些方法存在的意义是什么
  4. Reflect的方法为什么不放在Object上
  5. Reflect的设计目的是什么
  6. 为什么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上呢?何必新加一个对象?两者到底有什么区别?

  1. Reflect上不光有对象的相关操作方法还有函数相关的方法 这和Object本身代表的含义不符 因此不能放在Object上。同时Reflect为未来语言的扩展提供了一套可能的API,如果将来JavaScript想要添加新的底层操作对象的方法,它们可以被加入到Reflect上,而不是继续增加Object的静态方法,这样有助于保持Object构造函数的简洁性。
  2. 在Reflect出现之前,JavaScript操作对象的一些方法散布在Object构造函数上,比如Object.defineProperty。但是,这些方法的返回值和错误处理机制并不一致(例如,如果操作失败,一些方法会抛出错误,而其他一些方法则返回false)。Reflect提供了一套具有一致返回值的API,使得这些操作更加统一和可预测。

所以 当Reflect和Object方法能实现同样效果时 我们建议优先使用Reflect

设计目的

在有了上面的一些基本了解后 我们再来谈下Reflect的设计目的:

编程规范性

  1. 统一操作对象的方法:Reflect提供了一套具有一致返回值的API,使得这些操作更加统一和可预测。
  2. 提供未来的新操作 API:Reflect为未来语言的扩展提供了一套可能的API。
  3. 使某些操作更加函数式:JavaScript是一门支持函数式编程的语言,在某些场景中,我们可能更倾向于使用函数而不是命令式的操作。Reflect的方法都是函数更符合js函数式的思想。delete obj.xx, key in obj 这种代码都可以使用reflect替代。当Reflect和操作符(例如delete, in)能实现同样效果时 我们建议优先使用Reflect。
  4. 简化错误处理:像之前提到的,传统的对象操作方法在错误处理上不一致。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 代表的是代理对象本身或者继承自代理对象的对象,它表示触发陷阱时正确的上下文

#牛客在线求职答疑中心#
全部评论

相关推荐

不愿透露姓名的神秘牛友
10-13 15:30
点赞 评论 收藏
分享
点赞 1 评论
分享
牛客网
牛客企业服务