Proxy 本身不校验类型,需手动在 set/get 拦截器中实现运行时校验逻辑,结合 WeakMap 存储 schema 并递归代理嵌套对象,同时显式处理可选字段、联合类型与枚举。

Proxy 本身不校验类型,得自己写校验逻辑
Proxy 只是拦截读写操作的“钩子”,它不自带类型检查能力。所谓“运行时强类型校验”,本质是你在 set 或 get 拦截器里主动做判断:比如发现给 age 赋值 "25"(字符串),就抛错或转换。TypeScript 的类型只在编译期存在,运行时已擦除,Proxy 看到的永远是 JS 值。
常见错误现象:const user = new Proxy({ age: 25 }, handler) 后,仍能成功执行 user.age = "twenty-five" —— 因为没在 set 里写校验逻辑。
- 必须手动定义每个字段的预期类型(如
number、string | null)并存入元数据(例如用WeakMap关联 schema) - 不能依赖
typeof判断复杂类型(如Array、Date、自定义类),要用Array.isArray()、instanceof或Object.prototype.toString.call() - 对嵌套对象,需递归代理——否则
user.profile.name = 123不会触发校验
用 WeakMap 存 schema,避免内存泄漏
把校验规则和目标对象绑定时,直接挂属性(如 obj.$$schema)会污染原始对象,且无法自动清理。正确做法是用 WeakMap 做映射:
const schemaMap = new WeakMap();
function createValidatedModel(data, schema) {
schemaMap.set(data, schema);
return new Proxy(data, {
set(target, key, value) {
const type = schema[key];
if (type && !isValidType(value, type)) {
throw new TypeError(`Field "${key}" expected ${type}, got ${typeof value}`);
}
target[key] = value;
return true;
}
});
}
关键点:
立即学习“前端免费学习笔记(深入)”;
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
-
WeakMap键必须是对象,且不阻止垃圾回收——适合长期存活的 model 实例 - 不要用
Map,否则即使 model 被销毁,schema 仍驻留内存 - 如果 schema 包含函数(如自定义校验器),确保它们不意外捕获外部变量,否则同样阻碍 GC
处理可选字段和联合类型要显式声明
JS 运行时无法推断 TypeScript 中的 age?: number 或 status: 'active' | 'inactive'。你必须在 schema 中显式表达:
const userSchema = {
id: { type: 'number', required: true },
name: { type: 'string', required: false },
tags: { type: 'array', items: { type: 'string' } },
status: { enum: ['active', 'inactive'] }
};
容易踩的坑:
- 把
required: false理解成“允许undefined”,但忘了null是另一回事——需单独加nullable: true字段 - 联合枚举(
enum)校验必须用Array.isArray(schema.enum) && schema.enum.includes(value),不能只靠typeof - 对
string | number这种宽泛联合类型,实际业务中往往意味着“需要业务逻辑判断”,硬塞进 Proxy 校验反而模糊责任边界
深层嵌套对象必须递归代理,且避免无限循环
如果模型含子对象(如 user.address.city),只代理顶层对象是无效的。必须在 get 拦截器里检测返回值是否为对象,并动态创建新代理:
get(target, key) {
const value = target[key];
if (value !== null && typeof value === 'object' && !isProxy(value)) {
const nestedSchema = getNestedSchema(schemaMap.get(target), key);
return createValidatedModel(value, nestedSchema);
}
return value;
}
但要注意:
- 必须用
isProxy(可用Proxy.revocable配合标志位,或检查value.constructor === Proxy)防止重复代理同一对象 - 循环引用对象(如
a.b = b; b.a = a)会导致递归爆栈,需用WeakSet记录已代理对象作短路处理 - 性能敏感场景(如渲染千条列表项),每次
get都新建代理开销大——应改为“按需代理”,即仅在首次访问嵌套属性时生成
真正难的不是写 Proxy,而是定义清楚哪些字段必须运行时校验、哪些该由 TypeScript 编译期兜底、哪些应交给后端最终确认。把所有校验都堆进 Proxy,容易让模型变得笨重且难以调试。

















