为什么 TypeScript 同时具有 `void` 和 `undefined`?

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

在 TypeScript 中,您可以将函数注释为返回

void
:

function fn1(): void {
  // OK
}

function fn2(): void {
  // Error
  return 3;
}

你也可以注释一个函数返回

undefined

function fn3(): undefined {
  // OK
  return;
}

function fn4(): undefined {
  // Error
  return 3;
}

所以看起来如果你调用一个返回

void
的函数,你总是会得到值
undefined
。但是你不能写这段代码:

function fn5(): void {
}
let u: undefined = fn5(); // Error

为什么

void
不只是
undefined
的别名?它是否需要存在?

typescript undefined void
3个回答
154
投票

void
在函数返回类型中有特殊含义,不是
undefined
的别名。这样想是非常错误的。为什么?

void
的意图是函数的返回值不会被观察到。这与将是
undefined
非常不同。具有这种区别很重要,这样您才能正确描述像
forEach
这样的函数。让我们考虑
Array#forEach
的独立版本,在回调返回位置用
undefined
代替
void
编写:

declare function forEach<T>(arr: T[], callback: (el: T) => undefined): void;

如果您尝试使用此功能:

let target: number[] = [];
forEach([1, 2, 3], el => target.push(el));

你会得到一个错误:

类型“数字”不可分配给类型“未定义”

这是一个正确的错误——你说你想要一个返回值

undefined
的函数,但你实际上提供了一个返回值
number
的函数,因为这就是
Array#push
返回的值!

使用

void
代替意味着
forEach
承诺不使用返回值,因此可以使用返回任何值的回调来调用它

declare function forEach<T>(arr: T[], callback: (el: T) => void): void;
let target: number[] = [];
// OK
forEach([1, 2, 3], el => target.push(el));

为什么不直接使用

any
?如果您实际上是实施
forEach
的人,那么您真的不希望这样——让
any
浮动是一件危险的事情,它可以很容易地击败类型检查。

由此得出的结论是,如果你有一些返回类型为

void
的函数表达式,你不能肯定地说调用该函数的结果是
undefined

同样,
void
不是
undefined
的别名并且类型
void
的表达式可能具有any值,而不仅仅是
undefined

在函数 body 中,其返回类型明确列为

void
,TypeScript 将阻止您“意外”返回值,即使这不会造成类型系统冲突。这有助于捕获重构中出现的错误:

// Old version
function fn(arr: number[]): void {
  const arr1 = arr.map(x => {
    return 3;
  });
}

// New version
function fn(arr: number[]): void {
  for (const x of arr) {
    // Oops, meant to do something else
    return 3;
  };
}

2
投票

它们在语义上是不同的。

  • undefined
    表示不存在。这是对存在的否定。
  • void
    表示你无法判断它是否存在。这是对存在的可辨性的否定。

进一步思考,下面是readonly接口和functional接口的区别

interface RegularDictionary<K, V>
{
    get(key: K): V;
    set(key: K, value: V): void;
}

interface ReadonlyDictionary<K, V>
{
    get(key: K): V;
}

interface FunctionalDictionary<K, V>
{
    get(key: K): V;
    set: never;
}

ReadonlyDictionary
中,我们不知道方法
set
是否存在。我们不应该期望数据结构永远保持不变,因为它可能是一个类的实例,它确实有
set
但实现了
ReadonlyDictionary

这就是为什么

ReadonlyDictionary
不能严格用于函数式编程的原因。


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