导入含eval()的reviver函数时出现类未定义引用错误
问题原因分析
你的问题核心是**eval()的作用域限制**,和模块机制直接相关:
- 当
reviverFromMap在单独的serialization.ts模块中时,eval()运行在该模块的作用域内,而Position、Transform类是定义在测试模块serialization.test.ts中的,serialization.ts模块没有这些类的引用,自然找不到,抛出ReferenceError。 - 当把
reviver代码复制到测试文件时,eval()运行在测试模块的作用域里,能直接访问到模块内定义的Position和Transform,所以正常工作。
这既不是TypeScript/Vite的变量提升问题,也不是模块的“已知限制”,就是eval()的固有特性:它的作用域由自身所在的模块/上下文决定,而非调用它的地方。
解决方案:抛弃
eval(),传递类的构造函数映射 用字符串类名+eval()的方式本身就不安全,还会受作用域限制,正确的做法是在序列化时直接关联类的构造函数,而非仅存储类名字符串。
修改后的代码如下:
修改后的serialization.ts
type JsonCallback = (key: string, value: any) => any; // 改为存储类的构造函数,而非类名字符串 export const replacerWithMap = (): [replacer: JsonCallback, idsToConstructors: Map<number, new () => any>] => { let counter = 0; const constructorsToIds = new Map<new () => any, number>(); const idsToConstructors = new Map<number, new () => any>(); const replacer = (_: string, value: any): any => { if (typeof value === 'object' && !Array.isArray(value) && !!value.constructor) { const constructor = value.constructor; // 排除原生对象(比如Object) if (constructor === Object) { return value; } if (Object.hasOwn(value, '$_')) { throw new Error(`Illegal property name "$_" found on object during serialization: ${constructor.name}`); } let id = constructorsToIds.get(constructor); if (!id) { id = counter; constructorsToIds.set(constructor, id); idsToConstructors.set(id, constructor); ++counter; } return { '$_': id, ...value }; } return value; }; return [replacer, idsToConstructors]; }; // 直接使用构造函数实例化,无需eval export const reviverFromMap = (idsToConstructors: Map<number, new () => any>): JsonCallback => { const reviver = (_: string, value: any): any => { if (typeof value === 'object' && value !== null) { if (Object.hasOwn(value, '$_')) { const Constructor = idsToConstructors.get(value.$_); if (!Constructor) { throw new Error(`No constructor found for id: ${value.$_}`); } const instance = new Constructor(); for (const [key, val] of Object.entries(value)) { if (key !== '$_') { instance[key] = val; } } return instance; } } return value; }; return reviver; };
测试文件serialization.test.ts无需修改
原来的测试逻辑完全兼容,因为replacer现在会自动捕获类的构造函数,reviver直接用构造函数实例化,彻底避开了eval()的作用域问题。
额外优化点
- 处理私有属性:当前代码直接赋值私有属性(比如
Position._z)在TypeScript中会有类型警告,虽然运行时能生效,但更规范的做法是在类中实现toJSON方法,或者在序列化时只处理公共属性,反序列化时调用类的构造函数传入必要参数,而非直接赋值属性。 - 避免覆盖原生对象:在
replacer中增加了对Object构造函数的判断,防止把普通对象误识别为自定义类实例。
内容的提问来源于stack exchange,提问作者micka190
相关产品推荐
相关产品推荐

