为什么 Number.isFinite 没有类型保护?

问题描述 投票:0回答:3

有人可以问我为什么

Number.isFinite
没有像
number is number
那样的类型保护吗?

此示例提供了一个错误

Object is possibly 'undefined'

function inc (n?: number) {
  return Number.isFinite(n) ? n + 1 : 1;
}

游乐场:

https://www.typescriptlang.org/play?ssl=1&ssc=1&pln=3&pc=2#code/GYVwdgxgLglg9mABDSiAUYD8AuRYQC2ARgKYBOAlIgN4BQiiZJUIZSAcoaWQHQwDOAMRQwoJDFUx5EAakQBGRLnkBuWgF8gA

javascript typescript
3个回答
8
投票

首先介绍一下背景。乍一看,这似乎与 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,这个解释是基于他的回答。在阅读了他们的答案后,我将第二部分添加到了我原来的答案中。


7
投票

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")
- 转换前两个值显然不相等。


0
投票

因为在打字稿中没有办法返回与一些保护条件相结合的值。你能做的是 a)添加额外检查是否未定义值(但我认为在您的情况下您可能不再需要 Number.isFinite 了)

function inc (n?: number) {
    return typeof n === "number" && Number.isFinite(n) ? n + 1 : 1;
}

b) 或编写你自己的 isFiniteNumber 守卫。

© www.soinside.com 2019 - 2024. All rights reserved.