JavaScript/TypeScript中类构造函数返回错误的合规性及最优实现方式咨询
咱们一步步拆解你的问题:
为什么原始代码在TypeScript里不合规?
先看你写的第一个版本:
class Something { constructor(things) { if (things) { doSomething(); } else return { errorCode: 1 } } }
在JavaScript里,构造函数确实能返回对象覆盖默认实例,但TypeScript对类的构造函数有严格的类型约束:默认情况下,TS认定构造函数的返回类型就是类的实例类型(也就是Something)。当你在else分支返回带errorCode的普通对象时,这个对象的类型和Something完全不匹配,所以TS会抛出“类型Something上不存在属性errorCode”的错误——因为TS预期你调用new Something()后拿到的是Something实例,而非随机的错误对象。
更关键的是,这种写法破坏了类的设计初衷:类的核心是创建具有统一行为和结构的实例,而你的代码让new Something()有时返回实例、有时返回普通对象,调用方根本没法可靠处理返回值类型,很容易埋下bug。
两种写法的优劣对比
原始写法(返回错误对象)
- 致命问题:类型不兼容导致TS报错;返回值类型不稳定,调用方无法通过TS类型检查安全访问属性(比如访问
errorCode会被TS阻止,因为它认定返回的是Something实例)。 - 逻辑隐患:违背类的单一职责,把“创建实例”和“返回错误”混在一起,代码可读性、可维护性极差。
你的改进写法(给实例添加error属性)
class Something { constructor(things) { if (things) { doSomething(); } else this.error = { errorCode: 1 } } }
这种写法比原始写法好太多:
- 始终返回
Something实例,符合类的设计逻辑,TS类型系统能正确推断返回值类型。 - 调用方可通过检查
instance.error是否存在判断初始化是否出错,逻辑清晰。
不过要注意,在TypeScript里你需要先在类中声明error属性的类型,否则TS会报错“属性error在类型Something上不存在”,修正后的代码如下:
class Something { // 声明可选的error属性,明确类型 error?: { errorCode: number }; constructor(things: unknown) { if (things) { doSomething(); } else { this.error = { errorCode: 1 }; } } }
更符合TS最佳实践的替代方案
除了你的写法,还有两种更推荐的实现方式:
1. 使用工厂函数(适合返回不同类型结果的场景)
如果你的场景确实需要“成功返回实例、失败返回错误”,用工厂函数代替构造函数更合适,因为工厂函数可以明确返回联合类型:
class Something { constructor(things: unknown) { doSomething(); } } function createSomething(things: unknown): Something | { errorCode: number } { if (things) { return new Something(things); } else { return { errorCode: 1 }; } }
调用方可通过类型守卫区分结果,完全符合TS类型安全要求:
const result = createSomething(null); if ('errorCode' in result) { // TS知道这里result是错误对象,可安全访问errorCode console.log(result.errorCode); } else { // TS知道这里result是Something实例 // 执行实例相关操作 }
2. 抛出自定义错误(适合错误场景需要中断流程的情况)
如果初始化失败是需要中断当前流程的异常场景,更推荐抛出自定义错误,这符合JS/TS的错误处理范式:
// 定义自定义错误类 class InvalidThingsError extends Error { errorCode = 1; constructor() { super("未提供有效的things参数"); // 修复ES6错误类的原型链问题 Object.setPrototypeOf(this, InvalidThingsError.prototype); } } class Something { constructor(things: unknown) { if (!things) { throw new InvalidThingsError(); } doSomething(); } }
调用方用try/catch捕获错误即可:
try { const instance = new Something(null); // 初始化成功,操作实例 } catch (err) { if (err instanceof InvalidThingsError) { console.log(err.errorCode); // 1 } }
总结
你的改进写法已经解决了原始写法的核心问题,是合理的选择;如果场景允许,工厂函数或抛出自定义错误的方式会更符合TypeScript最佳实践,类型安全性和代码可读性都会更高。
内容的提问来源于stack exchange,提问作者Siddharth Shyniben

