Skip to content

Reflect反射

✍️ w 🕒 2024-06-20 14:17:34(2 days ago) 🔗 A.前端知识整理

因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了 Object 上面,Object 作为一个构造函数,这些操作实际上放到它身上并不合适,还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的

在ES6中新增了 Reflect ,让我们这些操作都集中到了Reflect对象上,它提供了一些反射方法,这些方法与那些在 ObjectFunction 原型上的方法具有相同的名称和功能。Reflect 的引入主要是为了使操作对象的行为变得更规范和一致,并且提供一个与 Proxy 对象互补的 API。

Object.getOwnPropertyNames(Reflect) 获取到'Reflect' 上的属性,因为这些属性都是不可枚举的因此没有使用'Object.keys' 来获取,一共获取了13个静态方法

['defineProperty', 'deleteProperty', 'apply', 'construct', 'get', 'getOwnPropertyDescriptor', 'getPrototypeOf', 'has', 'isExtensible', 'ownKeys', 'preventExtensions', 'set', 'setPrototypeOf']

Reflect 13个静态方法大致可以分为三类:对象操作、函数调用和原型操作

对象操作方法

  • Reflect.get(target, propertyKey[, receiver]):获取对象的属性值,相当于 target[propertyKey]
  • Reflect.set(target, propertyKey, value[, receiver]):设置对象的属性值,相当于 target[propertyKey] = value
  • Reflect.deleteProperty(target, propertyKey):删除对象的属性值,相当于 delete target[propertyKey]
  • Reflect.has(target, propertyKey):检查对象是否有某个属性,相当于 propertyKey in target
  • Reflect.defineProperty(target, propertyKey, descriptor):定义对象的属性,相当于 Object.defineProperty(target, propertyKey, descriptor)
  • Reflect.getOwnPropertyDescriptor(target, propertyKey):获取对象自有属性的描述符,相当于 Object.getOwnPropertyDescriptor(target, propertyKey)
  • Reflect.ownKeys(target):返回对象的所有自有属性的键,相当于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))

函数调用方法

  • Reflect.apply(target, thisArgument, argumentsList):调用一个函数,相当于 Function.prototype.apply.call(target, thisArgument, argumentsList)
  • Reflect.construct(target, argumentsList[, newTarget]):构造一个实例,相当于 new target(...argumentsList)

原型操作方法

  • Reflect.getPrototypeOf(target):获取对象的原型,相当于 Object.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype):设置对象的原型,相当于 Object.setPrototypeOf(target, prototype)
  • Reflect.isExtensible(target):检查对象是否是可扩展的,相当于 Object.isExtensible(target)
  • Reflect.preventExtensions(target):让一个对象变得不可扩展,相当于 Object.preventExtensions(target)

使用案例

Reflect.get(target, name, receiver)

好的,我来优化这段文字,使其更容易理解。


  1. Reflect.get(target, propertyKey, [receiver]) 方法的作用类似于从对象中读取属性(如 target[propertyKey]),但它是通过一个函数来实现的。如果 propertyKey 对应的属性是一个 getter 函数,receiver 参数将被作为 this 使用。如果没有提供 receiver 参数,那么 this 将指向 target。需要注意的是,只有在属性是 getter 函数时receiver 才能改变 this 的指向。

  2. 调用 Reflect.get() 类似于使用 target[propertyKey] 表达式,因为它也会查找目标对象原型链上的属性值。如果目标对象上不存在该属性,则返回 undefined

js
const a = {
  name: "w",
  age: "18",

  get info() {
    return this.name + this.age;
  },
};

console.log(Reflect.get(a, "name")); // w
console.log(Reflect.get(a, "info")); // w18

// 别的对象借用获取属性
console.log(Reflect.get(a, "info", { name: "z", age: "20" })); // z20

Reflect.set(target, propertyKey, value[, receiver])

等同于 target[propertyKey] = value

js
// 设置对象的属性值
Reflect.set(obj, 'b', 2);
console.log(obj.b); // 2

Reflect.deleteProperty(target, propertyKey)

对应的内部插槽 [[Delete]] 方法等同于delete obj[name],用于删除对象的属性,方法的第一个参数不是对象,会报错 Reflect.deleteProperty(target, propertyKey)

js
var obj = { x: 1, y: 2 };
Reflect.deleteProperty(obj, "x"); // true
obj; // { y: 2 }

var arr = [1, 2, 3, 4, 5];
Reflect.deleteProperty(arr, "3"); // true
arr; // [1, 2, 3, , 5]

// 如果属性不存在,返回 true
Reflect.deleteProperty({}, "foo"); // true

// 如果属性不可配置,返回 false
Reflect.deleteProperty(Object.freeze({foo: 1}), "foo"); // false

Reflect.has

Reflect.has方法检查target对象或其原型上的propertyKey属性是否存在。这与in操作符完全相同。如果找到属性,则返回true,否则返回false,对应内部插槽 [[HasProperty]]

js
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true

Reflect.ownKeys(target)

Reflect.ownKeys 使用的内部插槽 [[OwnPropertyKeys]] ,方法返回一个由目标对象自身的属性键组成的数组。它的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。结合了 Object.getOwnPropertyNamesObject.getOwnPropertySymbols 的结果,返回对象自身的所有属性键,包括字符串键和符号键。即自身所有属性(包含可枚举不可枚举和 symbol 属性)

  • Object.keys(obj):返回一个数组,包含对象自身的所有可枚举属性键,但不包括符号键。
  • Object.getOwnPropertyNames(obj):返回一个数组,包含对象自身的所有字符串属性键,但不包括符号键。
  • Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有符号属性键。
js
const obj = {
  property1: 42,
  [Symbol("property2")]: "symbol value",
  say() {},
};

// 增加不可枚举
Object.defineProperty(obj, "hidden", {
  value: "not enumerable",
  enumerable: false,
});

// 获取目标对象的所有属性键(包括符号键)
const keys = Reflect.ownKeys(obj);
console.log(keys); // [ 'property1', 'say', 'hidden', Symbol(property2) ]

// 只能可枚举的
console.log(Object.keys(obj)); // [ 'property1', 'say' ]

// 不能获取symbol的
console.log(Object.getOwnPropertyNames(obj)); // [ 'property1', 'say', 'hidden' ]
// 只能获取symbol的
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(property2) ]
  • 注意包含对象自身的所有符号属性键 用继承来说明
js
class Base {
  constructor() {
    this.baseProperty = "base";
  }

  baseMethod() {}
}

class Derived extends Base {
  constructor() {
    super();
    this.derivedProperty = "derived";
  }
  [Symbol("property2")] = "symbol value";

  derivedMethod() {}
}

const derivedObj = new Derived();

const keys = Reflect.ownKeys(derivedObj);

// 制造 可枚举的比较复杂 这里数据没有用可枚举

console.log(keys); // [ 'baseProperty', 'derivedProperty', Symbol(property2) ]

console.log(Object.keys(derivedObj)); // [ 'baseProperty', 'derivedProperty' ] // 只能可枚举的属性

console.log(Object.getOwnPropertyNames(derivedObj)); // [ 'baseProperty', 'derivedProperty' ] // 不能获取symbol属性

console.log(Object.getOwnPropertySymbols(derivedObj)); // [ Symbol(property2) ]

如上图如果你想获取继承的属性 console.log(Reflect.ownKeys(Reflect.getPrototypeOf(derivedObj))); 不停从原型链获取即可

Reflect.defineProperty(target, propertyKey, attributes)

Reflect.defineProperty(target, propertyKey, attributes) 使用的内部插槽[[DefineOwnProperty]]

Object.definePropertyReflect.defineProperty 两者在功能上非常相似,但它们在返回值和异常处理上有所不同。我们来详细对比一下:

  • Object.defineProperty 如果操作成功,返回目标对象 obj。如果操作失败,抛出一个 TypeError 异常。

  • Reflect.defineProperty如果操作成功,返回 true。 如果操作失败,返回 false

javascript
// 使用 Object.defineProperty
try {
  Object.defineProperty({}, 'key', { value: 42, writable: false });
  console.log('属性定义成功');
} catch (e) {
  console.error('Object.defineProperty 失败:', e);
}

// 使用 Reflect.defineProperty
const success = Reflect.defineProperty({}, 'key', { value: 42, writable: false });
if (success) {
  console.log('属性定义成功');
} else {
  console.log('Reflect.defineProperty 失败');
}

Reflect.getOwnPropertyDescriptor(target, propertyKey)

返回目标对象的非继承属性的属性描述符,即非 [[Prototype]](__proto__) 上的属性描述符。和 Object.getOwnPropertyDescriptor 有略微的不同

  • Object.getOwnPropertyDescriptor(target, propertyKey) 对非对象的 target 参数会被强制转换为对象。

  • Reflect.getOwnPropertyDescriptor target 必须是一个对象。如果传入非对象类型,会抛出异常(TypeError)。

    javascript
    const obj = { key: 42 };
    const descriptor = Reflect.getOwnPropertyDescriptor(obj, 'key');
    console.log(descriptor);
    // 输出: { value: 42, writable: true, enumerable: true, configurable: true }
    
    // 非对象的情况会抛出 TypeError
    try {
      Reflect.getOwnPropertyDescriptor(42, 'key');
    } catch (e) {
      console.error(e); // TypeError: Reflect.getOwnPropertyDescriptor called on non-object
    }
    javascript
    const obj = { key: 42 };
    const descriptor = Object.getOwnPropertyDescriptor(obj, 'key');
    console.log(descriptor);
    // 输出: { value: 42, writable: true, enumerable: true, configurable: true }
    
    // 基本类型会被强制转换为对象
    const strDescriptor = Object.getOwnPropertyDescriptor('foo', 'length');
    console.log(strDescriptor);
    // 输出: { value: 3, writable: false, enumerable: false, configurable: false }
js
Reflect.getOwnPropertyDescriptor({x: "hello"}, "x");
// {value: "hello", writable: true, enumerable: true, configurable: true}

Reflect.getOwnPropertyDescriptor({x: "hello"}, "y");
// undefined

Reflect.getOwnPropertyDescriptor([], "length");
// {value: 0, writable: true, enumerable: false, configurable: false}



// ------------------------------ 继承属性 ---------------------------------------
const sy = Symbol('a')

class A {
    name = 123;
    [sy] = 145
    getName() {}
}

class B extends A {
    getAge() {}
}
const b = new B()
console.log(Reflect.getOwnPropertyDescriptor(b, 'getName'))
console.log(Reflect.getOwnPropertyDescriptor(b, 'getAge'))
console.log(Reflect.getOwnPropertyDescriptor(b, 'name'))

// // 打印结果:
// undefined
// undefined
// { value: 123, writable: true, enumerable: true, configurable: true }

Reflect.getPrototypeOf(target)

1.静态方法 Reflect.getPrototypeOf(target) Object.getPrototypeOf() 方法几乎是一样的。都是返回指定对象的原型(即内部的 [[Prototype]] 属性的值)。如果目标没有原型,则返回null。 Reflect 要求target 必须是对象,Object 则不用 会自动进行拆箱转换

js
Reflect.getPrototypeOf( null) // TypeError: Reflect.getPrototypeOf called on non-object

Reflect.getPrototypeOf( 'hello') // TypeError: Reflect.getPrototypeOf called on non-object

Reflect.getPrototypeOf( {})  // Object {constructor: Object(), __defineGetter__: ƒ, …}

Reflect.setPrototypeOf(target, prototype)

Reflect.setPrototypeOf(target, prototype),返回一个 Boolean 值表明是否原型已经成功设置,内部的 [[Prototype]] 属性值对应 Object.setPrototypeOf()

js
const object1 = {};

console.log(Reflect.setPrototypeOf(object1, Object.prototype));
// expected output: true

console.log(Reflect.setPrototypeOf(object1, null));
// expected output: true

const object2 = {};

console.log(Reflect.setPrototypeOf(Object.freeze(object2), null));
// expected output: false

Reflect.preventExtensions(target)

在实际开发中,Reflect.preventExtensions 可以用来保护对象,使其不能再添加新的属性。虽然现有属性仍然可以修改或删除,但它提供了一种基本的对象保护机制,帮助你更好地控制对象的结构和类型。

  • Reflect.preventExtensions 返回操作成功的布尔值。
  • Object.preventExtensions 返回修改后的对象。
  • 如果参数不是对象,Reflect.preventExtensions 会抛出异常,而 Object.preventExtensions 会进行类型转换。
js
let obj = {name: "Alice"};

// 默认对象是可扩展的
console.log(Reflect.isExtensible(obj)); // 输出: true

// 使用 Reflect.preventExtensions 使对象不可扩展
Reflect.preventExtensions(obj);
console.log(Reflect.isExtensible(obj)); // 输出: false

// 尝试添加新属性会失败
obj.age = 30;
console.log(obj.age); // 输出: undefined (属性添加失败)

// 现有属性仍然可以修改
obj.name = "Bob";
console.log(obj.name); // 输出: "Bob"

// 现有属性可以删除
delete obj.name;
console.log(obj.name); // 输出: undefined

Reflect.isExtensible(target)

Reflect.isExtensible(target) target 必须是一个对象如果是非对象会异常报错,对应的内部插槽 [[IsExtensible]] ,检查对象是否可以进行扩展,返回一个 Boolean 值表明目标对象是否可扩展对应的方法,Object.isExtensible() 方法, 不同点是它对非对象的 target 参数将被强制转换为对象。

对象的“可扩展性”指的是是否可以向对象添加新的属性。默认情况下,所有对象都是可扩展的。但是,使用某些方法(如 Object.preventExtensionsObject.sealObject.freeze)可以防止对象的扩展。

  • Object.preventExtensions(obj): 使对象不可扩展。
  • Object.seal(obj): 使对象不可扩展且所有现有属性不可配置。
  • Object.freeze(obj): 使对象不可扩展、所有现有属性不可配置且不可写。
js
let obj = {};

// 默认对象是可扩展的
console.log(Reflect.isExtensible(obj)); // 输出: true

// 停止对象的扩展
Object.preventExtensions(obj);
console.log(Reflect.isExtensible(obj)); // 输出: false

// 使用 Object.freeze 冻结对象也会使其不可扩展
let frozenObj = Object.freeze({});
console.log(Reflect.isExtensible(frozenObj)); // 输出: false

// Object.seal 同样会使对象不可扩展
let sealedObj = Object.seal({});
console.log(Reflect.isExtensible(sealedObj)); // 输出: false

Reflect.apply(target, thisArgument, argumentsList)

1. Reflect.apply 的作用:Reflect.apply 对应 JavaScript 内部的 [[call]] 插槽,用于调用一个函数。其作用等同于 ES5 时期的 Function.prototype.apply.call,特别是在自定义函数的 apply 方法被重写但仍希望调用其原生 apply 方法时,Reflect.apply 更为简洁和直观。

2. Function.prototype.apply.call 的解析:

  • apply 方法:接受两个参数,第一个是 thisArg(函数运行时的 this 值),第二个是 传给函数参数的数组
  • call 方法:接受多个参数,第一个参数是 thisArg,之后是函数的参数分别列出。

例如:

javascript
Function.prototype.apply.call(someFunc, thisArg, [argsArray]);

等同于:

javascript
someFunc.apply(thisArg, argsArray);

3. Reflect.apply 的简化: 使用 Reflect.apply(target, thisArg, args)

  • target:目标函数
  • thisArg:函数调用时的 this
  • args:调用时的参数数组

4. 参数处理差异:

  • Function.prototype.apply 中,第二个参数可以为 nullundefined,函数仍会正常执行。
  • Reflect.apply 中,如果参数数组为 nullundefined 会抛出 TypeError 异常。

示例:

javascript
getName.apply(null, null); // 正常执行

Reflect.apply(getName, null, null); // TypeError 异常
Reflect.apply(getName, null, ""); // TypeError 异常
Reflect.apply(getName, null); // TypeError 异常

// 如果不传参数,应该这样写
Reflect.apply(getName, null, {}); // 或
Reflect.apply(getName, null, []); // 正常执行



const obj = { f: 'w' }

var f = 'windows'
function getName(age) {
    console.log(age, this.f)
}


getName(12)
getName.apply(obj, [12])
Reflect.apply(getName, null, [12])
Reflect.apply(getName, obj, [12])
Function.prototype.apply.call(getName, obj, [12])
// 打印结果
12 'windows'
12 'w'
12 'windows'
12 'w'
12 'w'

Reflect.construct(target, argumentsList, [, newTarget])

Reflect.construct(target, argumentsList[, newTarget]) 用于创建一个新的实例对象,并且该对象的原型是由 newTarget 确定的。

  • 代表目标构造函数的第一个参数。

  • 第二个参数,代表目标构造函数的参数,以类数组格式传递。

  • 可选参数,指定新创建对象的原型对象的 constructor 属性。如果不提供,默认值为 target

Reflect.construct 映射到 JavaScript 内部的 [[Constructor]] 插槽,等同于以下代码:

javascript
var obj = new Foo(...args);

Reflect.apply 一样,如果不需要对目标函数传参,那么需要传一个空数组,否则会报错。

javascript
Reflect.construct(target, null); // TypeError 异常
Reflect.construct(target); // TypeError 异常
Reflect.construct(target, []); // 不传参的情况

使用第三个参数 newTarget 可以改变新创建对象的 [[Prototype]] 指向,从而改变 __proto__。这意味着新创建的对象构造函数可以指向 newTarget,如下所示:

javascript
class OneClass {
    constructor() {
        this.name = 'OneClass';
    }
}

class OtherClass {
    constructor() {
        this.name = 'OtherClass';
    }
}

// 生成一个对象,其 `[[Prototype]]` 指向 `OtherClass.prototype`
let obj = Reflect.construct(OneClass, [], OtherClass);

console.log(obj instanceof OtherClass); // true
console.log(obj instanceof OneClass); // false
console.log(obj.name); // "OneClass"

这等同于如下代码:

javascript
// 创建一个以 `OtherClass.prototype` 为原型的对象
var obj2 = Object.create(OtherClass.prototype);
// 调用 `OneClass`,将 `this` 指向新对象
OneClass.apply(obj2, []);

注意:上述代码实际上调用的是 OneClass 的内部 [[Call]] 插槽,而不是 [[Constructor]]this 绑定到了新创建的对象上,因此 new.target 在这种情况下为 undefined,这和使用 Reflect.construct 是不同的。

js
class A {
    name = 'a'
    age = 'b'
    getAge() {}
}

class B {
    zz = 12
    getName() {}
}

const b = Reflect.construct(A, {}, B)
console.log(b instanceof A) // false
console.log(b instanceof B) // true

Released under the MIT License.