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

ES6+类继承问题:构造函数返回对象致子类方法丢失

解决ES6+继承显式返回对象的类时子类方法丢失问题(附NestJS+Prisma场景)

问题根源分析

当父类构造函数显式返回一个对象时,用new创建子类实例时,最终得到的是父类构造函数返回的对象,而非子类的实例对象。这个返回对象的原型链和子类完全无关,因此子类原型上的方法(比如你的saySomething或isUnmatchedPrecondition)无法被访问到。

以你提供的基础示例为例:

class Base {
  constructor() {
    return {name: "base"} // 显式返回普通对象
  }
}

class Extension extends Base {
  constructor() {
    super()
  }

  saySomething() {
    console.log("What do you want me to say?")
  }
}

const ext = new Extension();
console.log(ext instanceof Extension); // false,ext是{name: "base"},不是Extension实例
ext.saySomething(); // 报错:saySomething is not a function

在你的NestJS+Prisma场景中,PrismaExtended的构造函数返回了client(...args)(即Prisma扩展后的客户端实例),这个实例的原型链完全属于Prisma,和PrismaService无关,因此PrismaService中定义的自定义方法自然无法在运行时被找到。

解决方案

方案1:使用组合模式(推荐)

放弃继承,改用组合的方式,将Prisma客户端作为PrismaService的内部属性,自定义方法直接放在PrismaService中。这种方式更清晰,避免了原型链带来的问题,符合依赖注入的设计思想。

修改后的代码示例:

@Injectable()
export class PrismaService implements OnModuleInit, OnModuleDestroy {
  private readonly prisma: ReturnType<typeof createExtendedPrismaClient>;
  private retries: number = 0;

  constructor(
    private readonly logger: AppLogger,
    configService: ConfigService,
  ) {
    // 单独定义扩展Prisma客户端的逻辑
    const createExtendedPrismaClient = (...args: ConstructorParameters<typeof PrismaClient>) => 
      new PrismaClient(args).$extends({
        result: {
          $allModels: {
            toJSON: {
              compute(data: BaseEntity) {
                return <T extends typeof data = typeof data>() => {
                  return Object.assign({}, data, {
                    sn: typeof data.sn === 'bigint' ? data.sn.toString() : data.sn,
                  }) as unknown as Omit<T, 'sn'> & { sn: string };
                };
              },
            },
          },
        },
      });

    // 初始化Prisma客户端
    this.prisma = createExtendedPrismaClient({
      log: pickFrom(configService, 'app.status') !== NodeEnv.PROD ? ['query'] : undefined,
      transactionOptions: { timeout: 60_000 },
    });
  }

  async onModuleInit() {
    await this.prisma.$connect();
  }

  async onModuleDestroy() {
    await this.prisma.$disconnect();
  }

  // 自定义方法,直接属于PrismaService实例
  isUnmatchedPrecondition(e: unknown): e is PrismaClientKnownRequestError {
    return e instanceof PrismaClientKnownRequestError && e.code === PrismaErrorCode.UNMATCHED_PRECONDITION;
  }

  // 可选:通过Proxy自动代理Prisma客户端的方法,避免重复编写转发代码
  get(prop: string | symbol) {
    if (prop in this) {
      return Reflect.get(this, prop);
    }
    return Reflect.get(this.prisma, prop);
  }
}

使用时,你可以直接通过this.prisma.user.findMany()调用Prisma方法,或者通过Proxy直接this.user.findMany()(如果添加了Proxy逻辑),自定义方法直接this.isUnmatchedPrecondition(e)调用即可。

方案2:修改返回对象的原型链(备选)

如果一定要保留继承的写法,需要将子类的方法和原型链挂载到父类返回的对象上。修改PrismaExtended的构造函数:

function PrismaExtended() {
  type Params = ConstructorParameters<typeof PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel>>;
  const client = (...args: Params) =>
    new PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel>(...args).$extends({
      result: {
        $allModels: {
          toJSON: {
            compute(data: BaseEntity) {
              return <T extends typeof data = typeof data>() => {
                return Object.assign({}, data, {
                  sn: typeof data.sn === 'bigint' ? data.sn.toString() : data.sn,
                }) as unknown as Omit<T, 'sn'> & { sn: string };
              };
            },
          },
        },
      },
    });

  class PrismaExtended {
    constructor(...args: Params) {
      const prismaInstance = client(...args);
      // 将当前实例的可枚举方法复制到Prisma实例上
      Object.assign(prismaInstance, this);
      // 将Prisma实例的原型指向子类的原型,确保原型方法能被访问
      Object.setPrototypeOf(prismaInstance, Object.getPrototypeOf(this));
      return prismaInstance;
    }
  }

  return PrismaExtended as new (...args: Params) => ReturnType<typeof client>;
}

这种方式通过Object.assign复制实例方法,Object.setPrototypeOf修改原型链,让Prisma实例能访问到PrismaService的自定义方法。但需要注意,Prisma客户端本身的原型链可能会被修改,存在潜在的兼容性风险,因此不推荐作为首选方案。

内容的提问来源于stack exchange,提问作者calebpitan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:54:55