Reflect 详解 【面试速答版】 Q1: Reflect 是什么?它和 Object / Function 上的方法有什么区别? Reflect 是 ES2015(ES6)引入的一个全局内置对象,提供了一组与对象操作对应的方法 。它不是构造函数(不能 new Reflect()),也不是函数对象,它的所有方法都是静态的(类似 Math)。
核心区别有三点:
返回值更合理 :Object.defineProperty 失败时抛出异常,Reflect.defineProperty 返回 false参数更统一 :Reflect 的方法参数与对应的 Proxy handler 参数完全对齐,不用记忆零散的方法签名行为更规范 :Reflect.apply 替代 Function.prototype.apply.call(fn, thisArg, args) 这种别扭写法Q2: Reflect 解决了什么问题? 三个核心问题:
内部方法的外部化 :JS 引擎内部使用 [[Get]]、[[Set]]、[[Delete]] 等内部方法操作对象。过去开发者无法直接调用这些内部方法,只能通过 obj.key、delete obj.key 等语法间接触发。Reflect 将这些内部方法暴露为可调用的函数,使元编程成为可能。
与 Proxy 配合 :Proxy 的 handler 拦截方法和 Reflect 的方法是一一对应的。在 Proxy 中调用对应的 Reflect 方法可以确保行为与默认语义一致,避免手动实现带来的陷阱。
消除异常 vs false 的不一致 :Object.defineProperty、Object.seal、Object.freeze 等方法失败时抛出 TypeError,而 Reflect 对应方法返回 false,使异常处理更统一、可控。
Q3: Reflect 的常用方法有哪些?实际项目中怎么用? 13 个静态方法 (按用途分类):
对象基本操作 :
Reflect.get(target, prop, receiver) — 读取属性,相当于 target[prop]Reflect.set(target, prop, value, receiver) — 设置属性,返回布尔值Reflect.has(target, prop) — 检查属性是否存在,相当于 prop in targetReflect.deleteProperty(target, prop) — 删除属性,相当于 delete target[prop]Reflect.ownKeys(target) — 返回所有自有属性键(字符串 + Symbol),相当于 Object.getOwnPropertyNames + Object.getOwnPropertySymbols原型操作 :
Reflect.getPrototypeOf(target) — 获取原型Reflect.setPrototypeOf(target, proto) — 设置原型,返回布尔值属性描述符 :
Reflect.defineProperty(target, prop, descriptor) — 定义属性,返回布尔值Reflect.getOwnPropertyDescriptor(target, prop) — 获取属性描述符函数调用 :
Reflect.apply(target, thisArg, args) — 调用函数,相当于 target.apply(thisArg, args)Reflect.construct(target, args) — 调用构造函数,相当于 new target(...args)可扩展性 :
Reflect.isExtensible(target) — 判断是否可扩展Reflect.preventExtensions(target) — 阻止扩展,返回布尔值最常用的三个场景 :
Proxy 中转发默认行为 :在 Proxy handler 中调用 Reflect.get/set 确保默认行为一致**代替 Function.prototype.apply**:Reflect.apply(fn, thisArg, args) 更简洁 代替 delete 操作符 :Reflect.deleteProperty(obj, key) 返回布尔值,可安全判断是否删除成功【深入理解版】 1. Reflect 要解决什么问题? 1.1 从一段元编程需求讲起 假设你在做一个响应式框架,需要拦截对象属性的读取和设置,以便在读取时收集依赖、在设置时触发更新。
在没有 Reflect 的时代,你可能会用 Object.defineProperty 实现属性拦截:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function reactive (obj ) { const keys = Object .keys(obj) const deps = {} keys.forEach(key => { deps[key] = [] let value = obj[key] Object .defineProperty(obj, key, { get ( ) { if (currentWatcher) { deps[key].push(currentWatcher) } return value }, set (newValue ) { value = newValue deps[key].forEach(watcher => watcher.update()) } }) }) return obj }
这个方案有几个痛点:
痛点 1:Object.defineProperty 需要遍历所有属性
1 2 3 4 5 6 const user = reactive({ name : 'Alice' })user.age = 25 delete user.name
Vue 3 用 Proxy 解决了这个问题。但有了 Proxy,又引出了新的问题:
痛点 2:Proxy 中如何正确恢复默认行为?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const user = { name : 'Alice' , age : 25 }const proxy = new Proxy (user, { get (target, key, receiver ) { console .log(`读取 ${String (key)} ` ) return target[key] }, set (target, key, value, receiver ) { console .log(`设置 ${String (key)} = ${value} ` ) target[key] = value return true } })
为什么 target[key] 和 target[key] = value 可能有问题?因为:
丢失了 receiver(接收者) :如果属性是继承来的 getter/setter,target[key] 的 this 指向 target,而不是 proxy,导致原型链上的行为异常没有校验 :如果属性是只读的,target[key] = value 在严格模式下会报错,而你却返回了 true没有返回值 :set 必须返回布尔值表示成功与否,你手动返回 true 忽略了实际结果这就引出了 Reflect 的核心价值:Reflect 的方法参数和 Proxy handler 的参数完全对齐,且行为默认正确。
1.2 Reflect 的解决思路 Reflect 将 JS 引擎内部的操作(如 [[Get]]、[[Set]]、[[HasProperty]] 等)暴露为可调用的函数。
在 Proxy handler 中调用对应的 Reflect 方法,等价于「按默认行为执行」:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const proxy = new Proxy (user, { get (target, key, receiver ) { console .log(`读取 ${String (key)} ` ) return Reflect .get(target, key, receiver) }, set (target, key, value, receiver ) { console .log(`设置 ${String (key)} = ${value} ` ) return Reflect .set(target, key, value, receiver) } })
2. 核心原理与设计 2.1 内部方法映射 JS 规范将对象的基本操作定义为一系列「内部方法」,用 [[ ]] 表示。Reflect 的每个方法都对应一个内部方法:
内部方法 Proxy 拦截 Reflect 方法 语法等价 [[Get]]getReflect.get(target, key, receiver)target[key][[Set]]setReflect.set(target, key, value, receiver)target[key] = value[[HasProperty]]hasReflect.has(target, key)key in target[[Delete]]deletePropertyReflect.deleteProperty(target, key)delete target[key][[Call]]applyReflect.apply(fn, thisArg, args)fn(...args)[[Construct]]constructReflect.construct(Target, args)new Target(...args)[[GetPrototypeOf]]getPrototypeOfReflect.getPrototypeOf(target)Object.getPrototypeOf(target)[[SetPrototypeOf]]setPrototypeOfReflect.setPrototypeOf(target, proto)Object.setPrototypeOf(target, proto)[[IsExtensible]]isExtensibleReflect.isExtensible(target)Object.isExtensible(target)[[PreventExtensions]]preventExtensionsReflect.preventExtensions(target)Object.preventExtensions(target)[[GetOwnProperty]]getOwnPropertyDescriptorReflect.getOwnPropertyDescriptor(target, key)Object.getOwnPropertyDescriptor(target, key)[[DefineOwnProperty]]definePropertyReflect.defineProperty(target, key, desc)Object.defineProperty(target, key, desc)[[OwnPropertyKeys]]ownKeysReflect.ownKeys(target)Object.getOwnPropertyNames(target) + Object.getOwnPropertySymbols(target)
这种一一对应关系并非巧合——Reflect 的方法签名就是按照 Proxy handler 的参数设计的 。
2.2 Reflect 与 Object API 的核心差异 差异 1:异常 vs 布尔值返回
1 2 3 4 5 6 7 8 9 10 11 12 13 const obj = {}Object .defineProperty(obj, 'x' , { value : 1 , writable : false })try { Object .defineProperty(obj, 'x' , { value : 2 }) } catch (e) { console .log('Object 方式抛异常:' , e.message) } const success = Reflect .defineProperty(obj, 'x' , { value : 2 })console .log(success)
这在批量操作时非常有用——你不需要为每个操作包一层 try-catch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function defineProperties (obj, props ) { for (const [key, desc] of Object .entries(props)) { try { Object .defineProperty(obj, key, desc) } catch (e) { console .error(`定义属性 ${key} 失败` ) return false } } return true } function defineProperties (obj, props ) { for (const [key, desc] of Object .entries(props)) { if (!Reflect .defineProperty(obj, key, desc)) { console .error(`定义属性 ${key} 失败` ) return false } } return true }
差异 2:receiver 参数
Reflect.get 和 Reflect.set 的 receiver 参数是它们区别于 Object 对应操作的关键能力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const parent = { get name () { return this ._name.toUpperCase() }, set name (val ) { this ._name = val } } const child = { _name : 'child' }Object .setPrototypeOf(child, parent)child.name Reflect .get(parent, 'name' , { _name : 'forced' })
这个能力在 Proxy 中至关重要 :
1 2 3 4 5 6 7 8 9 const target = {}const proxy = new Proxy (target, { get (target, key, receiver ) { return Reflect .get(target, key, receiver) } })
差异 3:参数顺序更合理
1 2 3 4 5 6 7 8 9 10 11 Object .defineProperty(obj, 'prop' , { value : 1 })Reflect .defineProperty(obj, 'prop' , { value : 1 })const keys = Object .keys(obj)const values = Object .values(obj)
2.3 Reflect.construct 与 new 的区别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function Person (name, age ) { this .name = name this .age = age } const alice = new Person('Alice' , 25 )const bob = Reflect .construct(Person, ['Bob' , 30 ])function Child (name ) { this .name = name } const instance = Reflect .construct(Person, ['Alice' , 25 ], Child)console .log(instance instanceof Person) console .log(instance instanceof Child) console .log(instance.name) console .log(instance.age)
这在实现继承代理 或类工厂 时很有用。
3. 实际应用场景与代码示例 3.1 场景 1:Proxy 中默认行为转发(最经典用法) 这是 Reflect 最广泛的用途——在 Proxy handler 中调用对应的 Reflect 方法,确保默认行为被正确执行。
反例:手动实现转发 → 隐患
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const obj = { _value: 10 , get value () { return this ._value * 2 } } const proxy = new Proxy (obj, { get (target, key, receiver ) { console .log(`读取 ${String (key)} ` ) return target[key] } }) const child = Object .create(proxy)child._value = 100 console .log(child.value)
实际上这里的问题更微妙:child.value 通过原型链查找,触发了 proxy 的 get handler。handler 中 target[key] 读取 obj.value,getter 中 this 指向 obj,this._value 是 10。如果有 receiver 参数,应该返回 child._value * 2 = 200,但因为 receiver 没有被传递,所以应该得到 20。
但如果使用了 Reflect.get:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const proxy = new Proxy (obj, { get (target, key, receiver ) { console .log(`读取 ${String (key)} ` ) return Reflect .get(target, key, receiver) } }) const child = Object .create(proxy)child._value = 100 console .log(child.value)
3.2 场景 2:用 Reflect 实现更安全的继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Base { constructor (name ) { this .name = name } greet ( ) { return `Hello, ${this .name} ` } } class Derived extends Base { constructor (name, age ) { super (name) this .age = age } } function createInstance (Constructor, args, proto ) { const instance = Reflect .construct(Constructor, args, proto || Constructor) return instance } function SpecialProto ( ) {}SpecialProto.prototype.customMethod = function ( ) { return 'special method' } const obj = createInstance(Base, ['Alice' ], SpecialProto)console .log(obj.name) console .log(obj.customMethod()) console .log(obj instanceof Base) console .log(obj instanceof SpecialProto)
3.3 场景 3:用 Reflect.apply 替代 Function.prototype.apply 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const result1 = Math .max.apply(null , [1 , 2 , 3 ])const result2 = Math .max(...[1 , 2 , 3 ])function logger (prefix, message ) { console .log(`[${prefix} ] ${message} ` ) } Function .prototype.apply.call(logger, null , ['INFO' , 'hello' ])Reflect .apply(logger, null , ['INFO' , 'hello' ])const context = { count : 0 }function increment (amount ) { this .count += amount return this .count } Reflect .apply(increment, context, [5 ])console .log(context.count)
3.4 场景 4:用 Reflect.ownKeys 获取所有键 Reflect.ownKeys 返回一个对象的所有自有属性键,包括字符串和 Symbol,等价于 Object.getOwnPropertyNames 和 Object.getOwnPropertySymbols 的合并结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const sym = Symbol ('private' )const obj = { name: 'Alice' , age: 25 , [sym]: 'secret' } console .log(Object .keys(obj)) console .log(Object .getOwnPropertyNames(obj)) console .log(Object .getOwnPropertySymbols(obj)) console .log(Reflect .ownKeys(obj))
深层拷贝时全面复制属性 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function deepClone (target ) { if (target === null || typeof target !== 'object' ) return target const clone = Array .isArray(target) ? [] : Object .create(Reflect .getPrototypeOf(target)) for (const key of Reflect .ownKeys(target)) { const descriptor = Reflect .getOwnPropertyDescriptor(target, key) if (descriptor) { Reflect .defineProperty(clone, key, { ...descriptor, value : deepClone(descriptor.value) }) } } return clone }
3.5 场景 5:用 Reflect 实现可撤销权限控制 结合 Proxy.revocable 和 Reflect,实现一个可撤销的访问代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 function createAccessController (target ) { let revoked = false const handler = { get (target, key, receiver ) { if (revoked) throw new Error ('访问已被撤销' ) if (key === 'secret' ) throw new Error ('无权访问 secret 属性' ) return Reflect .get(target, key, receiver) }, set (target, key, value, receiver ) { if (revoked) throw new Error ('访问已被撤销' ) if (key === 'unchangeable' ) { console .log('unchangeable 是只读属性' ) return false } return Reflect .set(target, key, value, receiver) }, has (target, key ) { if (revoked) throw new Error ('访问已被撤销' ) return Reflect .has(target, key) }, deleteProperty (target, key ) { if (revoked) throw new Error ('访问已被撤销' ) return Reflect .deleteProperty(target, key) }, ownKeys (target ) { if (revoked) throw new Error ('访问已被撤销' ) return Reflect .ownKeys(target).filter(key => key !== 'secret' ) } } const { proxy, revoke } = Proxy .revocable(target, handler) return { proxy, revoke ( ) { revoked = true revoke() }, isRevoked: () => revoked } } const controller = createAccessController({ name: '公开数据' , secret: '这是秘密' , unchangeable: '固定值' }) console .log(controller.proxy.name) console .log(controller.proxy.secret) controller.proxy.unchangeable = '新值' controller.revoke() console .log(controller.proxy.name)
4. 常见误区 & 实际项目中的坑 4.1 误区:Reflect 可以替代 Object 的所有方法 错误理解 :
1 2 3 4 5 6 7 Reflect .keys(obj) Reflect .values(obj) Reflect .assign(target, source) Reflect .entries(obj) Reflect .freeze(obj) Reflect .seal(obj)
为什么这些方法不存在 :Reflect 只暴露 JS 引擎的「内部方法」([[Get]]、[[Set]]、[[Delete]] 等)。Object.keys、Object.values、Object.assign、Object.freeze 等是由多个内部方法组合而成的高级操作,不是单一的内部操作。
关键原则 :Reflect 的方法数量和 Proxy handler 的可拦截操作数量完全相同(13 个)。如果 Proxy 不能拦截它,Reflect 也不会有对应的方法。
正确认知 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Reflect .applyReflect .constructReflect .definePropertyReflect .deletePropertyReflect .getReflect .getOwnPropertyDescriptorReflect .getPrototypeOfReflect .hasReflect .isExtensibleReflect .ownKeysReflect .preventExtensionsReflect .setReflect .setPrototypeOfReflect .getPrototypeOf → Object .getPrototypeOfReflect .setPrototypeOf → Object .setPrototypeOfReflect .getOwnPropertyDescriptor → Object .getOwnPropertyDescriptorReflect .defineProperty → Object .definePropertyReflect .isExtensible → Object .isExtensibleReflect .preventExtensions → Object .preventExtensionsReflect .ownKeys → Object .getOwnPropertyNames + Object .getOwnPropertySymbolsReflect .get (Object 没有等价方法)Reflect .set (Object 没有等价方法)Reflect .has (Object 没有等价方法)Reflect .deleteProperty (Object 没有等价方法)Reflect .apply (Function .prototype.apply,不是 Object )Reflect .construct (new 操作符,不是 Object )
4.2 误区:Reflect.get 和 target[key] 完全等价 错误理解 :
1 2 3 4 const target = { x : 1 }const val1 = target.xconst val2 = Reflect .get(target, 'x' )
为什么不全等价 :当目标对象有 getter,且 getter 内部访问了 this 时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const target = { _secret: 42 , get secret () { return this ._secret } } console .log(target.secret) console .log(Reflect .get(target, 'secret' )) const receiver = { _secret : 100 }console .log(Reflect .get(target, 'secret' , receiver))
真正的等价关系 :
1 2 3 4 5 6 7 8 Reflect .get(target, key) === target[key] Reflect .get(target, key, target) === target[key] Reflect .get(target, key, other) !== target[key]
4.3 坑:Reflect.set 的返回值被忽略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const proxy = new Proxy ({}, { set (target, key, value, receiver ) { Reflect .set(target, key, value, receiver) } }) proxy.x = 1 console .log(proxy.x) 'use strict' proxy.x = 1
正确做法 :
1 2 3 4 5 6 const proxy = new Proxy ({}, { set (target, key, value, receiver ) { return Reflect .set(target, key, value, receiver) } })
4.4 坑:Reflect.apply 和 Reflect.construct 的类型判断 1 2 3 4 5 6 7 8 9 Reflect .apply('not a function' , null , []) Reflect .construct({}, []) const arrow = () => {}Reflect .construct(arrow, [])
4.5 坑:Reflect.ownKeys 的跨平台兼容性 在某些老旧环境(IE 等)中,Reflect 不存在。对于需要兼容旧浏览器的项目,需要用 polyfill:
1 2 3 4 import 'core-js/stable/reflect' import 'core-js/es/reflect'
或者直接使用 Babel 的 @babel/preset-env + useBuiltIns: 'usage' 自动引入。
5. 与相关知识的关联 & 对比 5.1 Reflect vs Proxy 维度 Reflect Proxy 角色 工具方法集 对象拦截器 是否修改对象 ❌ 只读/写,不改行为 ✅ 拦截并修改行为 是否可独立使用 ✅ 是 ✅ 是 与对方的关系 提供默认行为实现 拦截后用 Reflect 恢复默认行为 数量 13 个静态方法 13 个 handler 陷阱
核心关系 :Reflect 和 Proxy 是设计为一起使用的。每个 Proxy handler 的 trap 都有对应名称的 Reflect 方法。在 trap 中调用同名 Reflect 方法等于「按规范默认行为执行」。
1 2 3 4 5 6 7 8 9 10 11 12 13 const handler = { get: Reflect .get, set: Reflect .set, has: Reflect .has, deleteProperty: Reflect .deleteProperty, get (target, key, receiver ) { track(target, key) return Reflect .get(target, key, receiver) } }
5.2 Reflect vs Object API 操作 Object API Reflect API 关键差异 获取原型 Object.getPrototypeOfReflect.getPrototypeOf⚠️ 行为不同:Object 会对非对象参数做类型转换,Reflect 直接抛 TypeError 设置原型 Object.setPrototypeOfReflect.setPrototypeOf✅ 相同 定义属性 Object.definePropertyReflect.defineProperty⚠️ Object 抛异常,Reflect 返回 false 删除属性 delete target[key]Reflect.deleteProperty✅ Reflect 返回布尔值,delete 返回布尔值(但严格模式下不同) 属性描述符 Object.getOwnPropertyDescriptorReflect.getOwnPropertyDescriptor⚠️ Object 对非对象参数做类型转换,Reflect 抛 TypeError 自有键 getOwnPropertyNames + getOwnPropertySymbolsReflect.ownKeys✅ Reflect 一步到位 可扩展性 Object.isExtensibleReflect.isExtensible⚠️ 同上,参数处理不同 阻止扩展 Object.preventExtensionsReflect.preventExtensions⚠️ 同上 属性读取 target.keyReflect.get✅ Reflect 支持 receiver 属性设置 target.key = valReflect.set✅ Reflect 返回布尔值,支持 receiver
关于非对象参数的行为差异 :
1 2 3 4 5 6 7 Object .getPrototypeOf('hello' ) Object .getPrototypeOf(42 ) Reflect .getPrototypeOf('hello' ) Reflect .getPrototypeOf(42 )
6. 现代最佳实践(2025-2026) 6.1 在 Proxy 中始终使用 Reflect 转发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 const badHandler = { get (target, key ) { return target[key] }, set (target, key, value ) { target[key] = value } } const goodHandler = { get (target, key, receiver ) { if (typeof key === 'string' && key.startsWith('_' )) { throw new Error ('私有属性不可访问' ) } return Reflect .get(target, key, receiver) }, set (target, key, value, receiver ) { if (key === 'id' && typeof value !== 'number' ) { throw new TypeError ('id 必须是数字' ) } return Reflect .set(target, key, value, receiver) }, deleteProperty (target, key ) { if (key === 'id' ) { console .warn('不能删除 id 属性' ) return false } return Reflect .deleteProperty(target, key) }, has (target, key ) { if (typeof key === 'string' && key.startsWith('_' )) { return false } return Reflect .has(target, key) } }
6.2 用 Reflect 简化参数验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Validator { constructor (target, rules ) { return new Proxy (target, { set (target, key, value, receiver ) { const rule = rules[key] if (rule) { if (rule.type && typeof value !== rule.type) { throw new TypeError (`${String (key)} 必须是 ${rule.type} 类型` ) } if (rule.min != null && value < rule.min) { throw new RangeError (`${String (key)} 不能小于 ${rule.min} ` ) } if (rule.max != null && value > rule.max) { throw new RangeError (`${String (key)} 不能大于 ${rule.max} ` ) } if (rule.pattern && !rule.pattern.test(value)) { throw new Error (`${String (key)} 格式不正确` ) } } return Reflect .set(target, key, value, receiver) } }) } } const user = new Validator({ name: '' , age: 0 , email: '' }, { name: { type : 'string' , minLength : 1 }, age: { type : 'number' , min : 0 , max : 150 }, email: { type : 'string' , pattern : /^[^\s@]+@[^\s@]+\.[^\s@]+$/ } }) user.name = 'Alice' user.age = 25 user.email = 'alice@example.com' user.age = -1 user.email = 'invalid'
6.3 用 Reflect 实现观察者模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 function createObservable (target, onChange ) { return new Proxy (target, { set (target, key, value, receiver ) { const oldValue = Reflect .get(target, key, receiver) const result = Reflect .set(target, key, value, receiver) if (result && oldValue !== value) { onChange({ type: 'update' , key, oldValue, newValue: value, target }) } return result }, deleteProperty (target, key ) { const existed = key in target if (existed) { const oldValue = Reflect .get(target, key) const result = Reflect .deleteProperty(target, key) if (result) { onChange({ type: 'delete' , key, oldValue, target }) } return result } return true }, defineProperty (target, key, descriptor ) { const result = Reflect .defineProperty(target, key, descriptor) if (result) { onChange({ type: 'define' , key, descriptor, target }) } return result } }) } const user = { name : 'Alice' , age : 25 }const observed = createObservable(user, (event ) => { console .log(`[变更] ${event.type} ${String (event.key)} :` , event.oldValue, '→' , event.newValue ?? '(已删除)' ) }) observed.name = 'Bob' observed.age = 26 delete observed.name
6.4 用 Reflect 实现元数据标记 利用 Reflect.defineProperty 和 Reflect.getOwnPropertyDescriptor 实现不可枚举的元数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 const META_KEY = Symbol ('meta' )function defineMeta (target, key, meta ) { let allMeta = Reflect .getOwnPropertyDescriptor(target, META_KEY)?.value if (!allMeta) { allMeta = {} Reflect .defineProperty(target, META_KEY, { value: allMeta, writable: true , enumerable: false , configurable: false }) } allMeta[key] = meta } function getMeta (target, key ) { const allMeta = target[META_KEY] return allMeta?.[key] } const obj = {}defineMeta(obj, 'name' , { description : '用户名称' , required : true }) defineMeta(obj, 'age' , { description : '用户年龄' , min : 0 , max : 150 }) console .log(Reflect .ownKeys(obj)) console .log(getMeta(obj, 'name' ))
7. 常见疑问解答(自问自答) Q1: 为什么不需要 Reflect.keys?那我要获取可枚举键怎么办? Reflect.keys 不存在是因为 JS 规范中没有一个对应的内部方法 [[Keys]]。Object.keys 是通过 [[OwnPropertyKeys]] 获取所有自有键,再过滤出可枚举属性组合实现的。
获取可枚举键的正确方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const keys = Object .keys(obj)const ownKeys = Reflect .ownKeys(obj)const enumerableKeys = ownKeys.filter(key => { const desc = Reflect .getOwnPropertyDescriptor(obj, key) return desc && desc.enumerable }) for (const key in obj) { if (Object .hasOwn(obj, key)) { } }
Q2: Reflect.apply 比 fn.apply 好在哪?直接用展开运算符不行吗? 三个优势:
防止 apply 被重写 :
1 2 3 4 5 6 7 8 function malicious ( ) { return 'hacked' }malicious.apply = function ( ) { return 'apply hijacked' } malicious.apply(null , [1 , 2 ]) Reflect .apply(malicious, null , [1 , 2 ])
处理 null/undefined 的 thisArg :在某些严格模式下,fn.apply(null) 的 this 会指向全局对象,Reflect.apply(fn, null, []) 则始终将 null 作为 this 传递。
统一调用方式 :Reflect 的 API 风格一致,所有方法都以 target 为第一个参数。
展开运算符不能完全替代 apply 的场景:
需要显式控制 this 上下文 参数已经是数组,不想创建新数组(...args 每次创建新数组) 需要和 Proxy 的 apply trap 配合 Q3: Reflect.get 和 Reflect.set 的 receiver 参数什么时候必须传? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const handler = { get (target, key, receiver ) { return Reflect .get(target, key, receiver) }, set (target, key, value, receiver ) { return Reflect .set(target, key, value, receiver) } } const obj = { get data () { return this ._data } } Reflect .get(obj, 'data' , { _data : 'custom' }) const obj2 = { name : 'Alice' }Reflect .get(obj2, 'name' )
Q4: Reflect 有 polyfill 吗?如果浏览器不支持怎么办? 有。core-js 提供了完整的 Reflect polyfill:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 if (typeof Reflect === 'undefined' ) { globalThis.Reflect = { get (target, key, receiver ) { const desc = Object .getOwnPropertyDescriptor(target, key) if (desc && desc.get) { return desc.get.call(receiver || target) } return target[key] }, set (target, key, value, receiver ) { const desc = Object .getOwnPropertyDescriptor(target, key) if (desc) { if (desc.writable === false ) return false if (desc.set) { desc.set.call(receiver || target, value) return true } } target[key] = value return true }, } }
实际项目中 :如果用 Babel + core-js,@babel/preset-env 的 useBuiltIns: 'usage' 会根据目标浏览器自动引入需要的 Reflect polyfill。
注意 :Proxy 无法被完全 polyfill(因为它是引擎级别的能力),所以 Reflect 配合 Proxy 使用时,如果浏览器不支持 Proxy,polyfill 也没有意义。如果你不需要 Proxy,Reflect 可以独立使用,所有 13 个方法都可以被 polyfill。
Q5: Reflect.ownKeys 返回的键顺序有规范保证吗? 有。ES2015+ 规范明确规定了 Reflect.ownKeys 的返回值顺序:
整数索引(按升序) 字符串键(按创建顺序) Symbol 键(按创建顺序) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const obj = {}obj[Symbol ('c' )] = 3 obj['b' ] = 2 obj[Symbol ('a' )] = 4 obj['1' ] = 'first' obj['a' ] = 1 console .log(Reflect .ownKeys(obj))
注意 :这个顺序和 Object.keys 的顺序一致,也和 JSON.stringify 遍历属性的顺序一致。虽然规范保证了顺序,但实际编码中不应过度依赖,因为使用顺序通常意味着数据结构需要调整。
Q6: 为什么用 Reflect.deleteProperty 而不是 delete? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const obj = { x : 1 }delete obj.xconst result = delete obj.x const success = Reflect .deleteProperty(obj, 'x' )'use strict' delete obj.x Reflect .deleteProperty(obj, 'x' ) const keys = ['a' , 'b' , 'c' ]keys.forEach(key => Reflect .deleteProperty(obj, key))
8. 推荐学习路径 1 2 3 4 5 6 7 1. 阅读本文【面试速答版】 → 建立整体认知 2. 在浏览器控制台逐个测试 Reflect 的 13 个方法 3. 阅读 MDN 上 Reflect 的官方文档 4. 写一个简单的 Proxy + Reflect 的例子(如属性日志) 5. 手写一个 Reflect.get/Reflect.set 的 polyfill(理解内部机制) 6. 研究 Vue 3 源码中的 Reflect 使用 7. 尝试用 Proxy + Reflect 实现一个简单的响应式系统
关联知识点索引 :
Reflect 最常用的场景 → Proxy(本目录下 Proxy 相关文档) 响应式原理中 Reflect 的角色 → Vue3 响应式原理 ES6 元编程能力 → 运算符 属性描述符和对象操作 → 底层原理