You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

导入含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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 12:32:01