Reflect 详解

Reflect 详解

【面试速答版】

Q1: Reflect 是什么?它和 Object / Function 上的方法有什么区别?

Reflect 是 ES2015(ES6)引入的一个全局内置对象,提供了一组与对象操作对应的方法。它不是构造函数(不能 new Reflect()),也不是函数对象,它的所有方法都是静态的(类似 Math)。

核心区别有三点:

  1. 返回值更合理Object.defineProperty 失败时抛出异常,Reflect.defineProperty 返回 false
  2. 参数更统一Reflect 的方法参数与对应的 Proxy handler 参数完全对齐,不用记忆零散的方法签名
  3. 行为更规范Reflect.apply 替代 Function.prototype.apply.call(fn, thisArg, args) 这种别扭写法

Q2: Reflect 解决了什么问题?

三个核心问题:

  1. 内部方法的外部化:JS 引擎内部使用 [[Get]][[Set]][[Delete]] 等内部方法操作对象。过去开发者无法直接调用这些内部方法,只能通过 obj.keydelete obj.key 等语法间接触发。Reflect 将这些内部方法暴露为可调用的函数,使元编程成为可能。

  2. 与 Proxy 配合:Proxy 的 handler 拦截方法和 Reflect 的方法是一一对应的。在 Proxy 中调用对应的 Reflect 方法可以确保行为与默认语义一致,避免手动实现带来的陷阱。

  3. 消除异常 vs false 的不一致Object.definePropertyObject.sealObject.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 target
  • Reflect.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) — 阻止扩展,返回布尔值

最常用的三个场景

  1. Proxy 中转发默认行为:在 Proxy handler 中调用 Reflect.get/set 确保默认行为一致
  2. **代替 Function.prototype.apply**:Reflect.apply(fn, thisArg, args) 更简洁
  3. 代替 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
// Vue 2 风格的响应式(Object.defineProperty)
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 // ❌ age 属性没有被 defineProperty 处理,没有响应式

// 删除属性也无法监听
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
// 用 Proxy 实现响应式
const user = { name: 'Alice', age: 25 }

const proxy = new Proxy(user, {
get(target, key, receiver) {
// 依赖收集...
console.log(`读取 ${String(key)}`)

// 问题来了:如何返回正确的值?
// 直觉做法是直接返回 target[key]
return target[key] // ❌ 有问题!
},

set(target, key, value, receiver) {
// 触发更新...
console.log(`设置 ${String(key)} = ${value}`)

// 直觉做法是直接赋值
target[key] = value // ⚠️ 可能有问题
return true
}
})

为什么 target[key]target[key] = value 可能有问题?因为:

  1. 丢失了 receiver(接收者):如果属性是继承来的 getter/setter,target[key] 的 this 指向 target,而不是 proxy,导致原型链上的行为异常
  2. 没有校验:如果属性是只读的,target[key] = value 在严格模式下会报错,而你却返回了 true
  3. 没有返回值set 必须返回布尔值表示成功与否,你手动返回 true 忽略了实际结果

这就引出了 Reflect 的核心价值:Reflect 的方法参数和 Proxy handler 的参数完全对齐,且行为默认正确。

1.2 Reflect 的解决思路

Reflect 将 JS 引擎内部的操作(如 [[Get]][[Set]][[HasProperty]] 等)暴露为可调用的函数。

1
2
3
4
5
6
// 内部操作 vs Reflect API
// 内部操作:obj.key → 引擎执行 target.[[Get]](key, receiver)
// Reflect:Reflect.get(target, key, receiver)

// 内部操作:obj.key = value → 引擎执行 target.[[Set]](key, value, receiver)
// Reflect:Reflect.set(target, key, value, receiver)

在 Proxy handler 中调用对应的 Reflect 方法,等价于「按默认行为执行」:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ✅ 正确的做法:用 Reflect 转发
const proxy = new Proxy(user, {
get(target, key, receiver) {
console.log(`读取 ${String(key)}`)
// Reflect.get 的 receiver 参数会正确传递,保证 getter 的 this 绑定正确
return Reflect.get(target, key, receiver)
},

set(target, key, value, receiver) {
console.log(`设置 ${String(key)} = ${value}`)
// Reflect.set 返回布尔值,自动反映操作是否成功
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 })

// Object.defineProperty — 重复定义会抛出异常
try {
Object.defineProperty(obj, 'x', { value: 2 }) // ❌ TypeError: Cannot redefine property
} catch (e) {
console.log('Object 方式抛异常:', e.message)
}

// Reflect.defineProperty — 返回 false,不抛异常
const success = Reflect.defineProperty(obj, 'x', { value: 2 })
console.log(success) // false,不会抛出异常

这在批量操作时非常有用——你不需要为每个操作包一层 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
// Object 方式:需要 try-catch
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
}

// Reflect 方式:无需 try-catch
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.getReflect.setreceiver 参数是它们区别于 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)

// 使用 Object / 直接属性访问
child.name // 'CHILD' — 通过原型链调用 parent 的 getter
// getter 内部 this._name 指向 child,所以返回 'CHILD'

// 但如果想强制让 this 指向另一个对象?
// Object 没有提供这样的能力

// Reflect.get 的 receiver 可以指定 this
Reflect.get(parent, 'name', { _name: 'forced' }) // 'FORCED'
// 相当于:parent.name 被调用,但 getter 内部的 this 指向 receiver 对象

这个能力在 Proxy 中至关重要

1
2
3
4
5
6
7
8
9
const target = {}
const proxy = new Proxy(target, {
get(target, key, receiver) {
// receiver 是触发这个 get 的原始对象(可能是 proxy 本身或其原型链上的对象)
// 如果直接用 target[key],getter 的 this 永远指向 target
// 如果用 Reflect.get(target, key, receiver),getter 的 this 指向 receiver
return Reflect.get(target, key, receiver)
}
})

差异 3:参数顺序更合理

1
2
3
4
5
6
7
8
9
10
11
// Object.defineProperty — target, key, descriptor
Object.defineProperty(obj, 'prop', { value: 1 })

// Reflect.defineProperty — target, key, descriptor(一致)
Reflect.defineProperty(obj, 'prop', { value: 1 })

// 但有些 Object API 的参数顺序不同:
const keys = Object.keys(obj)
const values = Object.values(obj)

// 而 Reflect 的所有方法都是 target 作为第一个参数,保持一致

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
}

// 传统:new 操作符
const alice = new Person('Alice', 25)

// Reflect.construct
const bob = Reflect.construct(Person, ['Bob', 30])

// Reflect.construct 比 new 多了第三个参数:newTarget
// 用于修改 constructor 的 prototype
function Child(name) {
this.name = name
}

// 用 Person 的构造函数体,但原型用 Child.prototype
const instance = Reflect.construct(Person, ['Alice', 25], Child)
console.log(instance instanceof Person) // false
console.log(instance instanceof Child) // true
console.log(instance.name) // 'Alice'
console.log(instance.age) // 25

这在实现继承代理类工厂时很有用。

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)}`)
// ❌ 手动返回值,但不处理 receiver
return target[key]
}
})

const child = Object.create(proxy)
child._value = 100

console.log(child.value)
// 读取 value
// 100 * 2 = 200 ❌
// 预期是 child._value * 2 = 200,但触发的是 proxy 的 get
// 实际上 target[key] 中的 target 是 obj,所以 this._value 指向 obj._value
// 结果其实是 10 * 2 = 20,但输出显示 200???

// 仔细分析:
// 第一次 proxy.get trap 被触发,key = 'value'
// target[key] → obj.value → obj 的 getter 被调用 → this === obj → obj._value * 2 = 20
// 返回 20... 不对,怎么是 200?

实际上这里的问题更微妙:child.value 通过原型链查找,触发了 proxyget handler。handler 中 target[key] 读取 obj.value,getter 中 this 指向 objthis._value10。如果有 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)}`)
// ✅ 正确传递 receiver
return Reflect.get(target, key, receiver)
}
})

const child = Object.create(proxy)
child._value = 100

console.log(child.value)
// 读取 value
// 200 ✅
// Reflect.get 将 receiver 传递给了 getter,this 指向 child
// child._value * 2 = 200

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
}
}

// 用 Reflect.construct 实现动态继承
function createInstance(Constructor, args, proto) {
// 使用自定义的原型创建实例
const instance = Reflect.construct(Constructor, args, proto || Constructor)
return instance
}

// 动态指定 prototype
function SpecialProto() {}
SpecialProto.prototype.customMethod = function() {
return 'special method'
}

const obj = createInstance(Base, ['Alice'], SpecialProto)
console.log(obj.name) // 'Alice'
console.log(obj.customMethod()) // 'special method'
console.log(obj instanceof Base) // false
console.log(obj instanceof SpecialProto) // true

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
// 传统方式:Function.prototype.apply
const result1 = Math.max.apply(null, [1, 2, 3])

// ES6 方式:展开运算符
const result2 = Math.max(...[1, 2, 3])

// 但某些场景展开运算符行不通,比如:
// 1. this 上下文需要显式指定
function logger(prefix, message) {
console.log(`[${prefix}] ${message}`)
}

// 用 Function.prototype.apply — 写法别扭
Function.prototype.apply.call(logger, null, ['INFO', 'hello'])

// 用 Reflect.apply — 简洁直观
Reflect.apply(logger, null, ['INFO', 'hello'])

// 2. 需要控制 this 又不希望展开数组
const context = { count: 0 }
function increment(amount) {
this.count += amount
return this.count
}

// Reflect.apply 比 fn.apply 更安全(apply 可能被重写)
Reflect.apply(increment, context, [5])
console.log(context.count) // 5

3.4 场景 4:用 Reflect.ownKeys 获取所有键

Reflect.ownKeys 返回一个对象的所有自有属性键,包括字符串和 Symbol,等价于 Object.getOwnPropertyNamesObject.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'
}

// Object.keys — 只返回可枚举字符串键
console.log(Object.keys(obj)) // ['name', 'age']

// Object.getOwnPropertyNames — 返回所有字符串键(包括不可枚举)
console.log(Object.getOwnPropertyNames(obj)) // ['name', 'age']

// Object.getOwnPropertySymbols — 只返回 Symbol 键
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(private)]

// Reflect.ownKeys — 合并返回所有键
console.log(Reflect.ownKeys(obj)) // ['name', 'age', Symbol(private)]

深层拷贝时全面复制属性

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))

// 复制所有属性(包括 Symbol 键和不可枚举属性)
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) // Error: 无权访问 secret 属性
controller.proxy.unchangeable = '新值' // 'unchangeable 是只读属性'

controller.revoke()
console.log(controller.proxy.name) // Error: 访问已被撤销

4. 常见误区 & 实际项目中的坑

4.1 误区:Reflect 可以替代 Object 的所有方法

错误理解

1
2
3
4
5
6
7
// 以为 Reflect 是 Object 的完全替代品
Reflect.keys(obj) // ❌ TypeError: Reflect.keys is not a function
Reflect.values(obj) // ❌ TypeError: Reflect.values is not a function
Reflect.assign(target, source) // ❌ TypeError: Reflect.assign is not a function
Reflect.entries(obj) // ❌ TypeError: Reflect.entries is not a function
Reflect.freeze(obj) // ❌ TypeError: Reflect.freeze is not a function
Reflect.seal(obj) // ❌ TypeError: Reflect.seal is not a function

为什么这些方法不存在:Reflect 只暴露 JS 引擎的「内部方法」([[Get]][[Set]][[Delete]] 等)。Object.keysObject.valuesObject.assignObject.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 有的方法(13 个):
Reflect.apply
Reflect.construct
Reflect.defineProperty
Reflect.deleteProperty
Reflect.get
Reflect.getOwnPropertyDescriptor
Reflect.getPrototypeOf
Reflect.has
Reflect.isExtensible
Reflect.ownKeys
Reflect.preventExtensions
Reflect.set
Reflect.setPrototypeOf

// 这些是和 Object 共享的(两者都有,行为略有差异):
Reflect.getPrototypeOf → Object.getPrototypeOf
Reflect.setPrototypeOf → Object.setPrototypeOf
Reflect.getOwnPropertyDescriptor → Object.getOwnPropertyDescriptor
Reflect.defineProperty → Object.defineProperty
Reflect.isExtensible → Object.isExtensible
Reflect.preventExtensions → Object.preventExtensions
Reflect.ownKeys → Object.getOwnPropertyNames + Object.getOwnPropertySymbols

// 这些是 Reflect 独有、Object 没有的:
Reflect.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.x
const val2 = Reflect.get(target, 'x')
// 认为 val1 === val2 永远成立

为什么不全等价:当目标对象有 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) // 42 — getter 的 this 指向 target

// Reflect.get 没有 receiver
console.log(Reflect.get(target, 'secret')) // 42 — 等价,this 指向 target

// Reflect.get 指定 receiver
const receiver = { _secret: 100 }
console.log(Reflect.get(target, 'secret', receiver)) // 100 — getter 的 this 指向 receiver

真正的等价关系

1
2
3
4
5
6
7
8
// 当没有 getter 时,两者完全等价
Reflect.get(target, key) === target[key] // true(无 getter)

// 有 getter 但 receiver 是 target 时,等价
Reflect.get(target, key, target) === target[key] // true

// 有 getter 且 receiver 是其他对象时,不等价
Reflect.get(target, key, other) !== target[key] // true,getter 的 this 指向 other

4.3 坑:Reflect.set 的返回值被忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 错误代码:在 Proxy set handler 中不返回布尔值
const proxy = new Proxy({}, {
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver)
// ❌ 没有 return!隐式返回 undefined
// Proxy 的 set handler 必须返回布尔值
// undefined 会被转型为 false,表示设置失败
}
})

proxy.x = 1
console.log(proxy.x) // undefined ❌ 设置被拒绝了!

// 在严格模式下这甚至会抛出 TypeError
'use strict'
proxy.x = 1 // TypeError: 'set' returned false for property 'x'

正确做法

1
2
3
4
5
6
const proxy = new Proxy({}, {
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver)
// ✅ 直接返回 Reflect.set 的结果
}
})

4.4 坑:Reflect.apply 和 Reflect.construct 的类型判断

1
2
3
4
5
6
7
8
9
// Reflect.apply 只能用于函数
Reflect.apply('not a function', null, []) // ❌ TypeError

// Reflect.construct 只能用于构造函数
Reflect.construct({}, []) // ❌ TypeError: {}.constructor is not a constructor

// 箭头函数不能作为构造函数,也不能 Reflect.construct
const arrow = () => {}
Reflect.construct(arrow, []) // ❌ TypeError: arrow is not a constructor

4.5 坑:Reflect.ownKeys 的跨平台兼容性

在某些老旧环境(IE 等)中,Reflect 不存在。对于需要兼容旧浏览器的项目,需要用 polyfill:

1
npm install core-js
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

维度ReflectProxy
角色工具方法集对象拦截器
是否修改对象❌ 只读/写,不改行为✅ 拦截并修改行为
是否可独立使用✅ 是✅ 是
与对方的关系提供默认行为实现拦截后用 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
// 最佳实践:Proxy + Reflect 联合使用
const handler = {
get: Reflect.get, // 完全转发 get
set: Reflect.set, // 完全转发 set
has: Reflect.has, // 完全转发 has
deleteProperty: Reflect.deleteProperty, // 完全转发 deleteProperty

// 或者在 trap 中添加额外逻辑后再转发
get(target, key, receiver) {
track(target, key) // 额外逻辑:依赖收集
return Reflect.get(target, key, receiver) // 转发默认行为
}
}

5.2 Reflect vs Object API

操作Object APIReflect 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 API 会将非对象参数装箱
Object.getPrototypeOf('hello') // String.prototype
Object.getPrototypeOf(42) // Number.prototype

// Reflect API 对非对象参数直接抛 TypeError
Reflect.getPrototypeOf('hello') // ❌ TypeError: Reflect.getPrototypeOf called on non-object
Reflect.getPrototypeOf(42) // ❌ TypeError: Reflect.getPrototypeOf called on non-object

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
}
}

// 最佳实践:始终通过 Reflect 转发
const goodHandler = {
get(target, key, receiver) {
// 前置处理
if (typeof key === 'string' && key.startsWith('_')) {
throw new Error('私有属性不可访问')
}
// 用 Reflect 转发默认行为
return Reflect.get(target, key, receiver)
},

set(target, key, value, receiver) {
// 前置校验
if (key === 'id' && typeof value !== 'number') {
throw new TypeError('id 必须是数字')
}
// 用 Reflect 转发默认行为
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 // ❌ RangeError
user.email = 'invalid' // ❌ Error: 格式不正确

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' // [变更] update name: Alice → Bob
observed.age = 26 // [变更] update age: 25 → 26
delete observed.name // [变更] delete name: Bob → (已删除)

6.4 用 Reflect 实现元数据标记

利用 Reflect.definePropertyReflect.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)) // [Symbol(meta)] — meta 键不可枚举但可被 ownKeys 获取
console.log(getMeta(obj, 'name')) // { description: '用户名称', required: true }

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
// 方法 1:Object.keys(推荐)
const keys = Object.keys(obj)

// 方法 2:组合 Reflect.ownKeys + 过滤
const ownKeys = Reflect.ownKeys(obj)
const enumerableKeys = ownKeys.filter(key => {
const desc = Reflect.getOwnPropertyDescriptor(obj, key)
return desc && desc.enumerable
})

// 方法 3:for...in(包括原型链上的可枚举属性)
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
// 只处理自有属性
}
}

Q2: Reflect.apply 比 fn.apply 好在哪?直接用展开运算符不行吗?

三个优势:

  1. 防止 apply 被重写

    1
    2
    3
    4
    5
    6
    7
    8
    function malicious() { return 'hacked' }
    malicious.apply = function() { return 'apply hijacked' }

    // 如果直接用 fn.apply
    malicious.apply(null, [1, 2]) // 'apply hijacked'

    // Reflect.apply 不受影响
    Reflect.apply(malicious, null, [1, 2]) // 'hacked'
  2. 处理 null/undefined 的 thisArg:在某些严格模式下,fn.apply(null) 的 this 会指向全局对象,Reflect.apply(fn, null, []) 则始终将 null 作为 this 传递。

  3. 统一调用方式: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
// 场景 1:在 Proxy 的 get/set handler 中——永远必须传
const handler = {
get(target, key, receiver) {
return Reflect.get(target, key, receiver) // 必须传 receiver
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver) // 必须传 receiver
}
}

// 场景 2:对象有 getter/setter 且需要控制 this——必须传
const obj = {
get data() { return this._data }
}
Reflect.get(obj, 'data', { _data: 'custom' }) // 'custom'

// 场景 3:没有 getter/setter 的普通属性——可以不传
const obj2 = { name: 'Alice' }
Reflect.get(obj2, 'name') // 'Alice' — receiver 不影响结果

// 最佳实践:在 Proxy handler 中始终传递 receiver
// 在其他场景,如果你确定属性没有 getter/setter,可以不传

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
// core-js 的 Reflect polyfill 实现思路(简化版)
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-envuseBuiltIns: 'usage' 会根据目标浏览器自动引入需要的 Reflect polyfill。

注意:Proxy 无法被完全 polyfill(因为它是引擎级别的能力),所以 Reflect 配合 Proxy 使用时,如果浏览器不支持 Proxy,polyfill 也没有意义。如果你不需要 Proxy,Reflect 可以独立使用,所有 13 个方法都可以被 polyfill。

Q5: Reflect.ownKeys 返回的键顺序有规范保证吗?

有。ES2015+ 规范明确规定了 Reflect.ownKeys 的返回值顺序:

  1. 整数索引(按升序)
  2. 字符串键(按创建顺序)
  3. 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))
// ['1', 'a', 'b', Symbol(c), Symbol(a)]
// 1. '1' — 整数索引(升序)
// 2. 'a' — 字符串键(创建顺序:b 先于 a? 不对...)
// 实际上创建顺序是:Symbol(c), b, Symbol(a), 1, a
// 按规范:先整数 '1',然后字符串按创建顺序 'b', 'a',然后 Symbol 按创建顺序 Symbol(c), Symbol(a)
// 所以结果应该是:['1', 'b', 'a', Symbol(c), Symbol(a)]

注意:这个顺序和 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 操作符
delete obj.x
// 返回 true/false,但写法是语句不是表达式

// 在表达式中使用 delete
const result = delete obj.x // 可以,但 delete 是语句上下文

// Reflect.deleteProperty
const success = Reflect.deleteProperty(obj, 'x')
// 始终返回布尔值,是纯表达式

// 在严格模式下
'use strict'
delete obj.x // 语法上允许
Reflect.deleteProperty(obj, 'x') // 语法上允许,行为一致

// 主要优势:
// 1. 函数式风格,可以在高阶函数中使用
const keys = ['a', 'b', 'c']
keys.forEach(key => Reflect.deleteProperty(obj, key))

// 2. 不会因为严格模式导致语法解析差异
// 3. 与 Proxy 的 deleteProperty trap 对应

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 元编程能力 → 运算符
  • 属性描述符和对象操作 → 底层原理