Proxy 和 Reflect 是 JavaScript 元编程的底层基础设施,必须配合使用以保障属性描述符、receiver 绑定和原型链查找等关键语义正确。

Proxy 和 Reflect 不是语法糖,也不是“高级玩具”——它们是 JavaScript 元编程的底层基础设施。你写一个响应式系统、做字段级权限控制、拦截 API 调用、甚至实现运行时类型校验,绕不开它们。关键不是“会不会用”,而是“为什么必须这么用”。
为什么 set/get 拦截里非得用 Reflect.set 和 Reflect.get?
直接写 target[prop] = value 或 target[prop] 看似省事,但会悄悄破坏三类关键语义:
- 绕过属性描述符:比如
writable: false的属性,硬赋值不会报错(非严格模式下静默失败),而Reflect.set()会按规范返回false,让你能拦截并抛错 - 丢失
receiver:访问器(get name() { return this._name; })里的this必须指向代理对象本身,否则读不到代理上定义的私有字段;Reflect.get(target, prop, receiver)是唯一能正确传递receiver的方式 - 跳过原型链查找:
target[prop]只查自身属性,Reflect.get()才会走完整的[[Get]]内部流程,包括__proto__链和Symbol属性
const obj = {
get name() { return this._name || 'Anonymous'; },
set name(v) { this._name = v.toUpperCase(); }
};
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
console.log(`GET ${prop}`);
return Reflect.get(target, prop, receiver); // ✅ 正确
// return target[prop]; // ❌ this 指向 target,_name 读写失效
}
});
console.log(proxy.name); // "Anonymous"
怎么实现真正可靠的只读代理?
只拦 set 是常见误区。用户仍可通过 deleteProperty、defineProperty、preventExtensions 等方式篡改对象结构。
- 必须拦截全部可变操作:
set、deleteProperty、defineProperty、preventExtensions、setPrototypeOf - 类型校验逻辑放在
set里,但赋值动作必须交由Reflect.set()完成,否则会丢掉enumerable/configurable等描述符信息 -
get中仍要用Reflect.get(),否则嵌套对象、Symbol属性、继承来的访问器全失效
function readOnly(obj) {
return new Proxy(obj, {
set() { throw new Error('Cannot assign to read-only object'); },
deleteProperty() { throw new Error('Cannot delete from read-only object'); },
defineProperty() { throw new Error('Cannot define property on read-only object'); },
preventExtensions() { return true; },
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
return typeof result === 'object' && result !== null
? readOnly(result)
: result;
}
});
}
如何用 Proxy + Reflect 做轻量响应式(类似 Vue 3 原理)?
核心不在“劫持”,而在“触发时机可控”和“行为不越界”。Reflect 让你能安全转发默认行为,再插手副作用;Proxy 提供入口点。
-
get拦截中调用Reflect.get()后,立即执行依赖收集(比如把当前 effect 存进Dep) -
set拦截中先用Reflect.set()完成赋值,再触发更新(比如遍历Dep里存的函数并执行) - 对嵌套对象递归代理时,
Reflect.get()返回值需判断是否为对象,避免无限代理null或循环引用
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, key); // 收集依赖
return typeof res === 'object' && res !== null
? reactive(res)
: res;
},
set(target, key, value, receiver) {
const oldVal = target[key];
const res = Reflect.set(target, key, value, receiver);
if (oldVal !== value) trigger(target, key); // 触发更新
return res;
}
});
}
最常被忽略的一点:所有 Reflect 方法都要求参数顺序和语义严格匹配引擎内部操作——漏传 receiver、错用 Reflect.has() 替代 in、或在 construct trap 里不用 Reflect.construct(),都会导致继承链断裂、this 绑定错误、或构造失败静默。这不是“推荐用”,而是“必须用”。











