void、null、undefined、never
本教程共 80 篇 · 第 14 篇 · 更新于 2026-08-10 · 约 10 分钟阅读
本节目标:理解四个特殊类型的含义和使用场景,掌握 strictNullChecks 对 null/undefined 行为的改变,学会用非空断言和 never 做穷尽检查。
void:函数没有返回值
void 表示”这个函数没有返回值”。一个函数如果只有副作用(打印、写文件、发网络请求)而不返回任何东西,它的返回值类型就写 void。
function log(message: string): void {
console.log(message);
// 没有 return 语句
}
function handleClick(): void {
console.log("按钮被点击了");
}
JavaScript 中”没有返回值”的函数实际返回 undefined。所以从运行时的角度看,void 约等于 undefined。但 TypeScript 把 void 从 undefined 里独立出来——语义上你用它表达”这个函数的返回值没有意义,别去用它”。
function greet(): void {
console.log("你好");
}
let result = greet(); // result 的类型是 void
// result.toUpperCase(); // ❌ void 类型没有这个方法
把返回值类型写成 undefined 也是可以的,但那意味着你必须写 return undefined。void 允许你不写 return。
null 和 undefined
null 和 undefined 在 JavaScript 里是两个不同的值:null 表示”空值”——开发者主动设置的空,undefined 表示”未定义”——变量声明了但没赋值。
let empty: null = null;
let notAssigned: undefined = undefined;
strictNullChecks 的威力
在没有 strictNullChecks 的年代(TypeScript 6.x 之前默认关),null 和 undefined 可以赋给任何类型,这导致大量运行时错误无法在编译期发现。
TypeScript 7.0 默认开启 strict: true,其中包含 strictNullChecks: true。 在这个模式下,null 和 undefined 不能赋给其他类型:
// strictNullChecks: true(7.0 默认)
let name: string = "小明";
// name = null; // ❌ 不能将 null 赋给 string
// name = undefined; // ❌ 不能将 undefined 赋给 string
let age: number;
// age = undefined; // ❌ 也不能
如果你想让一个变量同时接受正常值和 null/undefined,必须用联合类型显式声明:
let name: string | null = "小明";
name = null; // ✅ 明确声明了允许 null
function findUser(id: number): User | undefined {
// 找不到时返回 undefined
return undefined;
}
这个变化看似麻烦,实际省下了无数线上 bug——它强制你在使用之前检查”这个值到底存不存在”。
非空断言 !
在某些场景下,你比 TypeScript 更确定一个值不可能是 null 或 undefined。比如你初始化了一个 DOM 元素,并且知道它一定存在。这时可以用非空断言 ! 告诉编译器”这个值不是 null”。
let element = document.getElementById("app")!;
// element 的类型是 HTMLElement,不是 HTMLElement | null
let user = getUser();
console.log(user!.name); // 我确定 user 不是 null/undefined
Warning非空断言是编译时承诺,不做任何运行时检查。如果你断言错了,运行时还是会崩。用
!之前问自己:真的能百分百确定吗?大多数场景用可选链?.和空值合并??更安全。
never:永远不存在的值
never 是 TypeScript 里最”刚”的类型。它表示永远不可能发生的值。主要有两种场景:
抛出异常的函数
function throwError(message: string): never {
throw new Error(message);
// 下面永远不会执行到
}
function fail(): never {
return throwError("出错了");
}
抛出异常的函数不会正常返回,所以它的返回类型是 never 而非 void。
死循环
function infiniteLoop(): never {
while (true) {
console.log("永远不会结束...");
}
}
never 的类型特性
never 是所有类型的子类型——这意味着 never 可以赋给任何类型。反过来,任何类型(除 never 自身以外)都不能赋给 never:
let n: never;
// let s: string = "hello";
// n = s; // ❌ string 不能赋给 never
n = throwError("..."); // ✅ 只有 never 能赋给 never
这个特性让 never 成为穷尽检查的利器。
穷尽检查:never 的实际应用
穷尽检查(exhaustiveness check)确保你处理了所有可能的情况。写 switch 语句时特别有用:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "triangle"; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
case "triangle":
return (shape.base * shape.height) / 2;
default: {
// 如果 Shape 后来新增了类型,这里会报错
let exhaustive: never = shape;
return exhaustive;
}
}
}
这段代码的妙处在于:如果将来有人在 Shape 里加了新的 shape 类型但忘了更新 getArea,default 分支里的 never 赋值就会报编译错误,提醒你漏了一个 case。
这就是 never 的实战价值——不是用来”写代码”,而是用来”防遗漏”。
四种特殊类型的层级关系
never → void → null/undefined → any/unknown
(最严格) (最宽松)
或者换一种理解方式:
never:这个值永远不存在void:这个函数不返回有用的东西undefined:这个变量还没赋值null:这个值被主动设为空unknown:我不知道这是什么,先检查any:我不在乎这是什么
可运行示例
// 四种特殊类型综合示例
// 1. void:无返回值的函数
function showWelcome(name: string): void {
console.log(`欢迎,${name}!`);
}
showWelcome("小明");
// 2. null / undefined 联合类型
function parseAge(input: string): number | null {
let age = Number(input);
if (isNaN(age) || age <= 0) {
return null; // 解析失败
}
return age;
}
let age1 = parseAge("25");
let age2 = parseAge("不是数字");
console.log(`年龄1: ${age1 ?? "无效"}`); // 25
console.log(`年龄2: ${age2 ?? "无效"}`); // 无效
// 3. never:穷尽检查
type Animal = "dog" | "cat" | "bird";
function getSound(animal: Animal): string {
switch (animal) {
case "dog": return "汪汪";
case "cat": return "喵喵";
case "bird": return "叽叽";
default: {
// 如果新增动物类型忘了处理,这里会报编译错误
let _check: never = animal;
return _check;
}
}
}
console.log(`狗: ${getSound("dog")}`);
console.log(`猫: ${getSound("cat")}`);
// 4. 非空断言(谨慎使用)
function getConfig(): { port?: number } {
return { port: 8080 };
}
let config = getConfig();
// if (config.port !== undefined) { ... } ← 更好的方式
console.log(`端口: ${config.port ?? 3000}`);
输出:
欢迎,小明!
年龄1: 25
年龄2: 无效
狗: 汪汪
猫: 喵喵
端口: 8080
小结
void 给无返回值的函数签名,null 和 undefined 在 strict 默认开启的 7.0 里会被严格拦截,never 是你防止遗漏分支的安全网。四个类型各司其职,组合起来能让你的代码既灵活又安全。下一章进入”组合类型”——联合类型和交叉类型,学会用 | 和 & 拼接出更复杂的类型。