【发布时间】:2021-04-19 13:02:10
【问题描述】:
有人能问我为什么Number.isFinite 没有像number is number 这样的类型保护吗?
这个例子提供了一个错误Object is possibly 'undefined'
function inc (n?: number) {
return Number.isFinite(n) ? n + 1 : 1;
}
游乐场:
【问题讨论】:
标签: javascript typescript
有人能问我为什么Number.isFinite 没有像number is number 这样的类型保护吗?
这个例子提供了一个错误Object is possibly 'undefined'
function inc (n?: number) {
return Number.isFinite(n) ? n + 1 : 1;
}
游乐场:
【问题讨论】:
标签: javascript typescript
首先是一些背景。乍一看,这似乎与 JavaScript 之间的区别有关
isFinite
它将首先将其参数转换为数字和
Number.isFinite
它很乐意接受所有参数,无论类型如何,并且只为有限数字返回true(没有隐式转换)。在 TypeScript 中,这些有签名
function isFinite(number: number): boolean
(method) NumberConstructor.isFinite(number: unknown): boolean
在第二种方法中,参数类型是unknown,而不是number。原因是在 JavaScript 中,Number.isFinitehappy 为非数字返回 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() 不是类型保护。据推测,如果你这样做if (Number.isFinite(x)) x += 2;,那总是正确的。没有Number.isFinite 将返回true 但参数不是数字类型的实例。
Number.isFinite() 返回false,该值仍然可以是一个数字。我的答案的初稿是当我以某种方式多次阅读isFinite,但不知何故将其视为isInteger。不得不用一个更适合非有限值的例子来重写它。
Number.isFinite() 不是类型保护,因为它会在极端情况下导致奇怪和错误的行为。
Number.isFinite(4) -> true 并且值是一个数字。没关系。Number.isFinite("apple") -> false 并且该值不是数字。没关系。Number.isFinite(Infinity) -> false 但值不是不是数字。这不好。
JavaScript 中的某些内容属于 number 类型,但不是有限的。这些是值Infinity、-Infinity 和NaN。是的,它代表“不是数字”,但它是数字类型。这很令人困惑,但很有意义。1
在大多数情况下,我们并不关心这些非限定值。
但是,由于它们仍然是数字,因此它们在数学运算中有效。无穷大的概念也是数学上的,有时很有用。
丢弃这些值有时会导致错误的代码。假设Number.isFinite() 被声明为类型保护。然后考虑这段代码:
function atLeast(minValue: string | number, x: number): boolean {
const min: number = Number.isFinite(x)
? 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));
函数中的代码被 TypeScript 接受并且当Number.isFinite() 返回false 类型缩小会导致minValue 不会被视为数字,只允许被视为字符串由编译器。
虽然此处的示例是人为设计的,而且不难发现它是错误的,但您很容易陷入现实世界的情况,当它仍然是一个数字时,您不小心将一个值缩小为一个数字。这就是为什么Number.isFinite() 不是类型保护的原因——它可能会错误地断言一些具有误导性的类型。
1 我希望这是一个直观的解释,为什么NaN 是一个数字类型。在 JavaScript 中,值可以更改,并且某些数学运算需要使用一个数字,否则它们没有意义。但并非所有内容都转换为数字。因此,当您将值转换为数字时,您需要以某种方式表示它无法转换。这就是NaN 的用武之地。
由于 NaN 在转换之前的上下文信息,你不能肯定地说它等于任何东西,因此为什么 NaN == NaN 是 false。等效代码可能是 Number("apple") == Number("orange") - 这两个值在转换之前显然不相等。
【讨论】:
NaN。尝试将符号转换为数字会引发 TypeError。
"apple" * "orange" 是一个无意义的操作 - 语法上有效但垃圾输入导致垃圾输出。对于符号,数学运算是一个严重的错误。不仅仅是垃圾输入 - 它是无效的。这是好,但随后会导致与现有处理事物的方式不同。 BigInt 的工作方式也类似2n * 4 会导致错误。
因为在打字稿中没有办法返回一个结合一些保护条件的值。你能做的是 a)为未定义的值添加额外检查(但我认为在您的情况下,您可能不再需要 Number.isFinite)
function inc (n?: number) {
return typeof n === "number" && Number.isFinite(n) ? n + 1 : 1;
}
b) 或编写自己的 isFiniteNumber 守卫。
【讨论】:
Number.isFinite(n) ? n! + 1 : 1