在 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
的别名?它是否需要存在?
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;
};
}
它们在语义上是不同的。
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
不能严格用于函数式编程的原因。