Skip to content

Reflect:与 Proxy 配套的"默认操作"API

"Reflect 是一个内置对象,提供了一系列方法来执行 JavaScript 对象的默认操作。它与 Proxy 紧密配合,使得在 Proxy 陷阱中转发操作变得更加简洁和可靠。"

Reflect 对象是在 ES6 中引入的,它提供了一系列静态方法,这些方法与 Proxy 的陷阱方法一一对应。Reflect 的设计目的是为了提供一种更合理的、更一致的方式来执行对象的默认操作。

一、Reflect 基础概念

Reflect 对象不是一个函数对象,因此不能使用 new 操作符调用,也不能将其作为函数调用。Reflect 的所有属性和方法都是静态的。

javascript
// 错误的用法
// const reflect = new Reflect(); // TypeError: Reflect is not a constructor

// 正确的用法
console.log(Reflect.get({ x: 1 }, 'x')); // 1

二、Reflect 与 Object 方法的对比

许多 Reflect 方法与 Object 上的方法功能相似,但有一些重要的区别:

javascript
const obj = { x: 1 };

// Object.defineProperty 返回对象本身
console.log(Object.defineProperty(obj, 'y', { value: 2 }) === obj); // true

// Reflect.defineProperty 返回布尔值表示成功与否
console.log(Reflect.defineProperty(obj, 'z', { value: 3 })); // true

// 当操作失败时的区别
try {
  Object.defineProperty(Object.freeze({}), 'x', { value: 1 });
} catch (e) {
  console.log('Object.defineProperty 抛出异常'); // 抛出异常
}

console.log(Reflect.defineProperty(Object.freeze({}), 'x', { value: 1 })); // false

三、Reflect 的十三种方法

Reflect 对象提供了与 Proxy 陷阱一一对应的方法:

1. Reflect.get(target, propertyKey[, receiver])

获取对象属性的值:

javascript
const obj = { x: 1 };
console.log(Reflect.get(obj, 'x')); // 1

// 使用 receiver 参数
const proxy = new Proxy(obj, {
  get(target, prop, receiver) {
    console.log(`通过代理访问属性: ${prop}`);
    // 使用 Reflect.get 转发操作,并传递 receiver
    return Reflect.get(target, prop, receiver);
  }
});

console.log(proxy.x); // 通过代理访问属性: x → 1

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

设置对象属性的值:

javascript
const obj = {};
console.log(Reflect.set(obj, 'x', 1)); // true
console.log(obj.x); // 1

// 使用 receiver 参数
const receiverObj = { multiplier: 2 };
const proxy = new Proxy(obj, {
  set(target, prop, value, receiver) {
    // 如果 receiver 有 multiplier 属性,则将值乘以它
    const actualValue = receiver.multiplier ? value * receiver.multiplier : value;
    return Reflect.set(target, prop, actualValue, receiver);
  }
});

proxy.y = 5;
console.log(obj.y); // 10 (5 * 2)

3. Reflect.has(target, propertyKey)

检查对象是否具有某个属性,类似于 in 操作符:

javascript
const obj = { x: 1 };
console.log(Reflect.has(obj, 'x')); // true
console.log(Reflect.has(obj, 'y')); // false
console.log('x' in obj); // true (等效于 in 操作符)

4. Reflect.deleteProperty(target, propertyKey)

删除对象的属性,类似于 delete 操作符:

javascript
const obj = { x: 1, y: 2 };
console.log(Reflect.deleteProperty(obj, 'x')); // true
console.log('x' in obj); // false
console.log(delete obj.y); // true (等效于 delete 操作符)

5. Reflect.getOwnPropertyDescriptor(target, propertyKey)

获取对象属性的描述符:

javascript
const obj = { x: 1 };
Object.defineProperty(obj, 'y', {
  value: 2,
  writable: false,
  enumerable: false
});

console.log(Reflect.getOwnPropertyDescriptor(obj, 'x'));
// { value: 1, writable: true, enumerable: true, configurable: true }

console.log(Reflect.getOwnPropertyDescriptor(obj, 'y'));
// { value: 2, writable: false, enumerable: false, configurable: true }

6. Reflect.defineProperty(target, propertyKey, attributes)

定义对象的属性:

javascript
const obj = {};
const success = Reflect.defineProperty(obj, 'x', {
  value: 1,
  writable: true,
  enumerable: true,
  configurable: true
});

console.log(success); // true
console.log(obj.x); // 1

7. Reflect.getPrototypeOf(target)

获取对象的原型:

javascript
const obj = {};
console.log(Reflect.getPrototypeOf(obj) === Object.prototype); // true
console.log(Reflect.getPrototypeOf([]) === Array.prototype); // true

8. Reflect.setPrototypeOf(target, prototype)

设置对象的原型:

javascript
const obj = {};
const proto = { x: 1 };
const success = Reflect.setPrototypeOf(obj, proto);
console.log(success); // true
console.log(obj.x); // 1

9. Reflect.isExtensible(target)

检查对象是否可扩展:

javascript
const obj = {};
console.log(Reflect.isExtensible(obj)); // true

Object.preventExtensions(obj);
console.log(Reflect.isExtensible(obj)); // false

10. Reflect.preventExtensions(target)

阻止对象扩展:

javascript
const obj = { x: 1 };
console.log(Reflect.preventExtensions(obj)); // true
console.log(Reflect.isExtensible(obj)); // false

11. Reflect.ownKeys(target)

获取对象的所有自有属性键:

javascript
const obj = { x: 1 };
Object.defineProperty(obj, 'y', {
  value: 2,
  enumerable: false
});

console.log(Reflect.ownKeys(obj)); // ["x", "y"]
console.log(Object.getOwnPropertyNames(obj)); // ["x", "y"] (类似)

12. Reflect.apply(target, thisArgument, argumentsList)

调用函数:

javascript
function add(a, b) {
  return a + b;
}

console.log(Reflect.apply(add, null, [1, 2])); // 3
console.log(Function.prototype.apply.call(add, null, [1, 2])); // 3 (等效)

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

使用 new 操作符调用构造函数:

javascript
class MyClass {
  constructor(value) {
    this.value = value;
  }
}

const instance = Reflect.construct(MyClass, [42]);
console.log(instance.value); // 42
console.log(instance instanceof MyClass); // true

// 使用 newTarget 参数
class AnotherClass {
  constructor(value) {
    this.anotherValue = value * 2;
  }
}

const anotherInstance = Reflect.construct(MyClass, [42], AnotherClass);
console.log(anotherInstance.value); // 42
console.log(anotherInstance instanceof AnotherClass); // true
console.log(anotherInstance instanceof MyClass); // false

四、Reflect 与 Proxy 的配合使用

Reflect 最重要的用途之一是与 Proxy 配合使用,简化代理陷阱中的操作转发:

javascript
const target = {
  _private: 'secret',
  public: 'visible'
};

const handler = {
  get(target, prop, receiver) {
    // 拦截私有属性访问
    if (prop.startsWith('_')) {
      throw new Error(`访问私有属性 ${prop} 被禁止`);
    }
    // 使用 Reflect 转发正常的属性访问
    return Reflect.get(target, prop, receiver);
  },
  
  set(target, prop, value, receiver) {
    // 拦截私有属性设置
    if (prop.startsWith('_')) {
      throw new Error(`设置私有属性 ${prop} 被禁止`);
    }
    // 使用 Reflect 转发正常的属性设置
    return Reflect.set(target, prop, value, receiver);
  },
  
  has(target, prop) {
    // 隐藏私有属性
    if (prop.startsWith('_')) {
      return false;
    }
    // 使用 Reflect 转发正常的属性检查
    return Reflect.has(target, prop);
  }
};

const proxy = new Proxy(target, handler);

console.log(proxy.public); // "visible"
console.log('public' in proxy); // true

try {
  console.log(proxy._private); // Error: 访问私有属性 _private 被禁止
} catch (e) {
  console.log(e.message);
}

try {
  proxy._private = 'new secret'; // Error: 设置私有属性 _private 被禁止
} catch (e) {
  console.log(e.message);
}

console.log('_private' in proxy); // false

五、实际应用示例

1. 实现属性访问日志

javascript
function createLoggedObject(obj) {
  const handler = {
    get(target, prop, receiver) {
      console.log(`GET ${prop}`);
      return Reflect.get(target, prop, receiver);
    },
    
    set(target, prop, value, receiver) {
      console.log(`SET ${prop} = ${value}`);
      return Reflect.set(target, prop, value, receiver);
    }
  };
  
  return new Proxy(obj, handler);
}

const loggedObj = createLoggedObject({ x: 1, y: 2 });
console.log(loggedObj.x); // GET x → 1
loggedObj.z = 3; // SET z = 3

2. 实现只读对象

javascript
function createReadOnlyObject(obj) {
  const handler = {
    set(target, prop) {
      throw new Error(`不能修改只读对象的属性: ${prop}`);
    },
    
    deleteProperty(target, prop) {
      throw new Error(`不能删除只读对象的属性: ${prop}`);
    }
  };
  
  return new Proxy(obj, handler);
}

const readOnlyObj = createReadOnlyObject({ x: 1, y: 2 });
console.log(readOnlyObj.x); // 1

try {
  readOnlyObj.x = 3; // Error: 不能修改只读对象的属性: x
} catch (e) {
  console.log(e.message);
}

3. 实现验证对象

javascript
function createValidatedObject(obj, schema) {
  const handler = {
    set(target, prop, value, receiver) {
      // 验证属性
      if (prop in schema) {
        const validator = schema[prop];
        if (!validator(value)) {
          throw new Error(`属性 ${prop} 的值 ${value} 不符合验证规则`);
        }
      }
      return Reflect.set(target, prop, value, receiver);
    }
  };
  
  return new Proxy(obj, handler);
}

const person = createValidatedObject({}, {
  age: value => Number.isInteger(value) && value >= 0 && value <= 150,
  email: value => typeof value === 'string' && value.includes('@')
});

person.age = 30; // 正常设置
person.email = 'test@example.com'; // 正常设置

try {
  person.age = -5; // Error: 属性 age 的值 -5 不符合验证规则
} catch (e) {
  console.log(e.message);
}

六、Reflect 的优势

1. 更一致的返回值

javascript
// Object.defineProperty 在失败时抛出异常
try {
  Object.defineProperty(Object.freeze({}), 'x', { value: 1 });
} catch (e) {
  console.log('Object.defineProperty 失败'); // 抛出异常
}

// Reflect.defineProperty 在失败时返回 false
const result = Reflect.defineProperty(Object.freeze({}), 'x', { value: 1 });
console.log(`Reflect.defineProperty ${result ? '成功' : '失败'}`); // 失败

2. 更简洁的函数调用

javascript
// 传统方式调用函数
Function.prototype.apply.call(Math.max, Math, [1, 2, 3]); // 3

// 使用 Reflect.apply 更简洁
Reflect.apply(Math.max, Math, [1, 2, 3]); // 3

一句话总结

Reflect 提供了一套与 Proxy 陷阱方法一一对应的静态方法,用于执行 JavaScript 对象的默认操作。它比相应的 Object 方法更加一致和可靠,特别是在错误处理方面,是现代 JavaScript 元编程的重要组成部分。

通过使用 Reflect,我们可以更简洁、更安全地在 Proxy 陷阱中转发操作,同时享受到更一致的 API 设计和更好的错误处理机制。