Reflect:与 Proxy 配套的"默认操作"API
"Reflect 是一个内置对象,提供了一系列方法来执行 JavaScript 对象的默认操作。它与 Proxy 紧密配合,使得在 Proxy 陷阱中转发操作变得更加简洁和可靠。"
Reflect 对象是在 ES6 中引入的,它提供了一系列静态方法,这些方法与 Proxy 的陷阱方法一一对应。Reflect 的设计目的是为了提供一种更合理的、更一致的方式来执行对象的默认操作。
一、Reflect 基础概念
Reflect 对象不是一个函数对象,因此不能使用 new 操作符调用,也不能将其作为函数调用。Reflect 的所有属性和方法都是静态的。
// 错误的用法
// const reflect = new Reflect(); // TypeError: Reflect is not a constructor
// 正确的用法
console.log(Reflect.get({ x: 1 }, 'x')); // 1二、Reflect 与 Object 方法的对比
许多 Reflect 方法与 Object 上的方法功能相似,但有一些重要的区别:
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])
获取对象属性的值:
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 → 12. Reflect.set(target, propertyKey, value[, receiver])
设置对象属性的值:
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 操作符:
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 操作符:
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)
获取对象属性的描述符:
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)
定义对象的属性:
const obj = {};
const success = Reflect.defineProperty(obj, 'x', {
value: 1,
writable: true,
enumerable: true,
configurable: true
});
console.log(success); // true
console.log(obj.x); // 17. Reflect.getPrototypeOf(target)
获取对象的原型:
const obj = {};
console.log(Reflect.getPrototypeOf(obj) === Object.prototype); // true
console.log(Reflect.getPrototypeOf([]) === Array.prototype); // true8. Reflect.setPrototypeOf(target, prototype)
设置对象的原型:
const obj = {};
const proto = { x: 1 };
const success = Reflect.setPrototypeOf(obj, proto);
console.log(success); // true
console.log(obj.x); // 19. Reflect.isExtensible(target)
检查对象是否可扩展:
const obj = {};
console.log(Reflect.isExtensible(obj)); // true
Object.preventExtensions(obj);
console.log(Reflect.isExtensible(obj)); // false10. Reflect.preventExtensions(target)
阻止对象扩展:
const obj = { x: 1 };
console.log(Reflect.preventExtensions(obj)); // true
console.log(Reflect.isExtensible(obj)); // false11. Reflect.ownKeys(target)
获取对象的所有自有属性键:
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)
调用函数:
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 操作符调用构造函数:
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 配合使用,简化代理陷阱中的操作转发:
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. 实现属性访问日志
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 = 32. 实现只读对象
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. 实现验证对象
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. 更一致的返回值
// 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. 更简洁的函数调用
// 传统方式调用函数
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 设计和更好的错误处理机制。