有人可以问我为什么
Number.isFinite
没有像number is number
那样的类型保护吗?
此示例提供了一个错误
Object is possibly 'undefined'
function inc (n?: number) {
return Number.isFinite(n) ? n + 1 : 1;
}
游乐场:
首先介绍一下背景。乍一看,这似乎与 JavaScript 之间的差异有关
isFinite
首先将其参数转换为数字并且
Number.isFinite
它很乐意接受所有参数,无论类型如何,并且仅针对有限数字返回
true
(没有隐式转换)。在 TypeScript 中,这些有签名
function isFinite(number: number): boolean
(method) NumberConstructor.isFinite(number: unknown): boolean
第二种方法中,参数类型为
unknown
,而不是number
。原因是在 JavaScript 中,对于非数字,Number.isFinite
happy 返回 false
(而 isFinite
首先强制)。
> Number.isFinite(false)
false
> isFinite(false)
true
> Number.isFinite(Symbol(2))
false
> isFinite(Symbol(2))
Uncaught TypeError: Cannot convert a Symbol value to a number at isFinite (<anonymous>)
现在,由于
Number.isFinite
已经被期望接受来自任何类型的所有参数并返回 false
,无论参数有多么古怪,在 TypeScript 中保留这种行为是有意义的。因此,正确的参数类型是unknown
。对于仅适用于数字的全局 isFinite
(并且 TypeScript 帮助我们避免强制转换),将参数限制为 number
是有意义的。
但是正如你的问题正确地提出的,为什么,鉴于
Number.isFinite
将接受任何参数并仅针对数字返回 true
,为什么它不能成为类型保护?原因是它对 some 返回 true,但不是对 all 数字!让我们至少尝试编写自己的守卫:
function numberIsFinite(n: any): n is number {
return Number.isFinite(n)
}
这些都是有道理的:
console.log(numberIsFinite(20)) // true
console.log(numberIsFinite(Number.MAX_VALUE)) // true
console.log(numberIsFinite(undefined)) // false
console.log(numberIsFinite("still ok")) // false
但是这里出了问题:
console.log(numberIsFinite(Infinity)) // false but uh-oh
console.log(numberIsFinite(NaN)) // false but uh-oh
现在确实有了这个类型保护
const x: number|string = 3
if (numberIsFinite(x)) {
console.log(`A number whose type is ${typeof(x)}`)
} else {
console.log(`A string whose type is ${typeof(x)}`)
}
将打印
"A number whose type is number"
到目前为止一切都很好,但是:
const x: number|string = Infinity
if (numberIsFinite(x)) {
console.log(`A number whose type is ${typeof(x)}`)
} else {
console.log(`A string whose type is ${typeof(x)}`)
}
将打印
"A string whose type is number"
这很愚蠢。
如果 TS 中有一个称为“有限数”的依赖类型,那么(也许只有那时)
Number.isFinite
成为类型保护才有意义。
感谢@VLAZ,这个解释是基于他的回答。在阅读了他们的答案后,我将第二部分添加到了我原来的答案中。
Number.isFinite()
不是类型保护,因为它可能会在边缘情况下导致奇怪和错误的行为。
Number.isFinite(4)
-> true
,该值是一个数字。没关系。Number.isFinite("apple")
-> false
并且该值不是数字。没关系。Number.isFinite(Infinity)
-> false
但该值是 not 不是数字。这样不好。
JavaScript 中的某些事物属于
number
类型,但不是有限的。这些是值 Infinity
、-Infinity
和 NaN
。是的,它代表“不是数字”,但它是数字类型。虽然令人困惑,但很有道理。1
大多数情况下,我们并不关心这些非有限值。
但是,由于它们仍然是数字,因此它们在数学运算中是有效的。无穷大的概念也是数学上的,有时很有用。
丢弃这些值有时会导致错误的代码。我们假设
Number.isFinite()
被声明为类型保护。然后考虑这段代码:
function atLeast(minValue: string | number, num: number): boolean {
const min: number = Number.isFinite(num)
? minValue
: Number(minValue.replace(/\D/g, "")); //convert
return num >= min;
}
atLeast(2, 42); //true
atLeast("$3.50", 42); //true
atLeast(-Infinity, 42); //error
这将是有效的代码。逻辑错误,有点奇怪,但只是为了说明一点。不过,更实际的用法可能是:
let dynamicMinimum = -Infinity; //allow all
/* ... */
someArray.filter(x => atLeast(dynamicMinimum, x));
当 Number.isFinite()
返回
false
时,函数中的代码会被 TypeScript and接受。类型缩小会导致
minValue
不会被视为数字,而只允许编译器将其视为字符串。
虽然这里的示例是人为的,并且不难看出它是错误的,但您很容易陷入现实世界的情况,即您意外地将值缩小为数字,而实际上它仍然是数字。这就是为什么
Number.isFinite()
不是类型保护 - 它可能会错误地断言有关误导类型的内容。
1 这是我希望直观的解释为什么
NaN
是数字类型。在 JavaScript 中,值可以更改,并且某些数学运算需要使用数字,否则它们就没有意义。但并非所有内容都会转换为数字。因此,当您将值转换为数字时,您需要以某种方式表明它无法转换。这就是 NaN
发挥作用的地方。
由于
NaN
在转换之前的上下文信息,你不能肯定地说它等于任何东西,因此为什么 NaN == NaN
是 false
。等效代码可能是 Number("apple") == Number("orange")
- 转换前两个值显然不相等。
因为在打字稿中没有办法返回与一些保护条件相结合的值。你能做的是 a)添加额外检查是否未定义值(但我认为在您的情况下您可能不再需要 Number.isFinite 了)
function inc (n?: number) {
return typeof n === "number" && Number.isFinite(n) ? n + 1 : 1;
}
b) 或编写你自己的 isFiniteNumber 守卫。