JS/TS 对String
、Number
和Boolean
类型进行自动装箱和拆箱,允许混合使用文字和对象在同一个表达式中,没有显式转换,例如:
const a = "3"+ new String("abc");
我正在尝试通过提供自定义类 Long
为 bigint
和 number
实现类似的功能:
class Long {
public constructor(private value: bigint | number) { }
public valueOf(): bigint {
return BigInt(this.value);
}
}
const long = new Long(123);
console.log(456n + long);
这工作得很好(并打印 579n
),但会导致我的 linter 和 TS 编译器显示最后一个表达式的错误。我可以用这样的评论来压制它们:
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
console.log(456n + long);
但这对于整个应用来说并不是一个好的解决方案。
有没有办法告诉 Long
将被视为 bigint
或其他任何避免错误的方法?
关于为什么这样做:
我正在开发一种工具来转换 Java to Typescript,并希望支持尽可能多的 Java 语义。 Long
类型包含一个 long
整数,它是 64 位宽,在 TS 中只能使用 bigint
表示。这样做的主要问题是 Java 会自动拆箱 Long
就像 String
一样,我想尽可能地支持这种语义。
对于@caTS:所以这永远不会是普通的 TS 代码,而是始终用作 java.lang.Long
,因此不会造成混淆。
最佳答案
您想要的行为,即 TypeScript 允许您使用自定义类 that overrides the Object.prototype.valueOf()
method,就好像它是 valueOf()
返回的原始类型一样,不幸的是,这不是该语言的一部分。在 microsoft/TypeScript#2361 上有一个相当长的开放功能请求,但尚未实现,而且看起来不会很快实现。
目前,这意味着只有变通办法。您可以使用的一种解决方法是对编译器撒谎,说 Long
有一个返回原始实例的 construct signature。也就是说,您希望它具有 new (value: bigint | number) => bigint;
类型(或者 new (value: bigint | number) => bigint & Long
code> 以便您保留添加到 Long
的任何额外方法或功能。
以下是您可以如何做到这一点:
// rename
class _Long {
public constructor(private value: bigint | number) { }
public valueOf(): bigint {
return BigInt(this.value);
}
}
// assign and assert
const Long = _Long as
new (value: bigint | number) => bigint & _Long;
我在这里重命名了您原来的 Long
构造函数,因为一旦您声明了 class Long { }
值 Long
就得到了自动构造一个构造函数类型,您不能更改该类型。
然后我将重命名的构造函数分配给名为 Long
的所需变量,并且 asserted 它是所需类型,new (value: bigint | number) => bigint & _Long
而不是实际类型,new (value: bigint | number) => _Long
。
我需要使用类型断言,因为编译器会提示简单的赋值;它知道 Long
的实例类型不是 bigint
。
好的,现在我们有一个名为 Long
的类构造函数,编译器认为它会生成 bigint
实例。让我们测试一下:
const long = new Long(123);
// const long: bigint
console.log(456n + long); // okay, 579
看起来不错。我可以调用 new Long(123)
,编译器认为结果是 bigint
。它还让我可以毫无怨言地使用像 +
这样的数学运算符,结果就是您所期望的。
所以这和我想象的一样有效。尽管如此,我通常不会故意对编译器撒谎,因为这样的谎言以后可能会以奇怪的方式使您陷入困境。 long
的类型显然不是 bigint
:
console.log(typeof long); // "object", not "bigint"
因此任何依赖于 long
的操作实际上是一个 bigint
可能会做一些编译器无法捕获的有趣事情,所以你会看到意想不到的运行时行为:
const bigint1 = BigInt(4);
const bigint2 = BigInt(4);
console.log(bigint1 === bigint2); // true
const long1 = new Long(4);
const long2 = new Long(4);
console.log(long1 === long2); // false!
function copy<T extends {}>(x: T) {
return (typeof x === "object") ? { ...x } : x
}
const bigint3 = copy(bigint1);
console.log(bigint3 + 1n) // 5
const long3 = copy(long1);
console.log(long3 + 1n) // "[object Object]1" 🤪
现在在问题中提到的特定用例中,由于将生成所有 TypeScript 代码,因此您可以保证不会生成任何绊倒这些绊脚石的代码。但即便如此,了解这些事情并在决定是否要继续时将它们考虑在内也很重要。
关于typescript - 假运算符重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74144649/