在前端开发中,
null和undefined常常被混用,但在 TypeScript 的严格类型体系下,它们有着本质的不同。本文将带你全面理解这两个"空值"的区别、使用场景以及如何在 TypeScript 中安全地处理它们。
📌 目录
- 从 JavaScript 说起
- TypeScript 中的类型差异
strictNullChecks严格空值检查- 两者在函数中的行为差异
- 可选链与空值合并运算符
- 实际开发中的使用建议
- 总结
从 JavaScript 说起
在原生 JavaScript 中,null 和 undefined 都表示"空"或"没有值",但它们的起源不同:
undefined:表示"未定义"。当变量声明了但未被赋值,或者访问对象不存在的属性时,JavaScript 引擎会自动返回undefined。null:表示"空对象指针",通常由开发者主动赋值,意为"此处应该有一个对象,但目前没有"。
javascript
let a; // a 的值为 undefined
let b = null; // b 的值为 null
console.log(typeof a); // "undefined"
console.log(typeof b); // "object" (这是一个众所周的 Bug)
二者在宽松相等 (==) 比较时为 true,但在严格相等 (===) 下为 false:
ini
null == undefined // true
null === undefined // false
这种模糊性让很多开发者感到困惑,于是 TypeScript 提供了更严格的类型管控。
TypeScript 中的类型差异
在 TypeScript 中,null 和 undefined 是各自独立的类型:
ini
let u: undefined = undefined;
let n: null = null;
undefined类型只有一个值:undefinednull类型只有一个值:null
如果你尝试将 null 赋给一个声明为 undefined 类型的变量,或者反过来,TypeScript 会报错(在开启严格模式时):
javascript
let u: undefined = null; // 错误:不能将类型"null"分配给类型"undefined"
let n: null = undefined; // 错误:不能将类型"undefined"分配给类型"null"
另外,在 TypeScript 中,如果你声明一个变量但不赋值,它的类型会被推断为 any,而不是 undefined? 实际上要分情况:
typescript
let x; // 类型推断为 any
let y: number; // 类型为 number,但未赋值时会报错(strict 下)
更常见的场景是:在 strictNullChecks 模式下,null 和 undefined 不能随意赋值给其他类型。
strictNullChecks ------ 严格空值检查
TypeScript 有一个关键的编译选项:strictNullChecks(通常在 strict: true 下自动开启)。它彻底改变了 null 和 undefined 的行为。
关闭 strictNullChecks(宽松模式)
此时,null 和 undefined 可以被赋值给任何类型的变量,类似于 JavaScript 的灵活行为,但这就失去了类型检查的意义。
typescript
// 宽松模式:不报错
let name: string = null;
let age: number = undefined;
开启 strictNullChecks(推荐)
null 和 undefined 成为了独立的"非空"类型的禁区。以下写法会报错:
typescript
let name: string = null; // 错误:不能将 null 赋给 string
let age: number = undefined; // 错误:不能将 undefined 赋给 number
想要表达一个变量可能为空,必须使用联合类型:
typescript
let name: string | null = null; // 允许
let age: number | undefined = undefined; // 允许
let address: string | null | undefined = undefined; // 允许两者
这种强制显式标注,让代码的空值处理变得清晰可追溯,极大地减少了运行时 Cannot read property of undefined 这类错误。
两者在函数中的行为差异
默认参数
当我们为函数参数提供默认值时,只有 undefined 会触发默认值,null 不会:
javascript
function greet(name: string = "Guest") {
console.log(`Hello, ${name}`);
}
greet(undefined); // Hello, Guest
greet(null); // Hello, null
这体现了 undefined 在语言层面的特殊地位:它代表"缺失",而 null 代表"存在的空值"。
可选参数和可选属性
在 TypeScript 中,带有 ? 标记的参数或属性,其类型会自动被加上 undefined,而不是 null:
typescript
interface Person {
name: string;
age?: number; // 类型实际是 number | undefined
}
function foo(x?: number) { // x 类型是 number | undefined
// ...
}
所以 TypeScript 的语言设计倾向于使用 undefined 表示"可选/缺失",而 null 需要开发者主动声明。
可选链与空值合并运算符
这两个运算符让处理 null 和 undefined 变得更加优雅。
可选链 ?.
当访问一个可能为 null 或 undefined 的对象属性时,可选链会短路返回 undefined,而不会抛出错误。
typescript
sql
interface User {
address?: {
street: string;
};
}
let user: User | null = null;
console.log(user?.address?.street); // undefined(不报错)
注意:可选链对 null 和 undefined 都会短路,不作区分。
空值合并运算符 ??
只有值严格为 null 或 undefined 时,才返回右侧的默认值。这与 || 不同(|| 会在假值 0, '', false 时也返回右侧)。
typescript
typescript
let value: string | null | undefined;
value = null;
console.log(value ?? "default"); // "default"
value = 0;
console.log(value ?? 100); // 0
console.log(value || 100); // 100
因此,在需要区分"空指针"和"假值"的业务逻辑中,优先使用 ??。
实际开发中的使用建议
1. 总是开启 strictNullChecks
在 tsconfig.json 中配置:
json
bash
{
"compilerOptions": {
"strict": true // 或者单独开启 "strictNullChecks": true
}
}
2. 明确语义:undefined 表示"未初始化/缺失",null 表示"主动清空"
很多规范建议:
- 对于可能尚未存在的属性/变量,用
undefined(例如可选参数、未赋值的变量)。 - 当你想要重置一个对象引用时,显式赋值为
null,表示"曾经有,现在没有了"。
3. 避免同时使用 null 和 undefined
在同一个项目中最好约定统一使用一种来表示"空"。最常见的是只用 undefined 表示缺失,不得已时才用 null(例如与某些后端 API 或库交互时)。
4. 使用类型守卫缩小范围
typescript
javascript
function process(value: string | null) {
if (value === null) {
// 此时 TypeScript 知道 value 是 null
return;
}
// 这里 value 被收窄为 string
console.log(value.toUpperCase());
}
5. 使用非空断言操作符 !(慎重)
当你比 TypeScript 更清楚某个值不可能为 null/undefined 时,可以使用 ! 断言,但滥用会增加运行时风险。
typescript
ini
let elem = document.getElementById("app")!; // 认为它一定存在
总结
| 特性 | undefined |
null |
|---|---|---|
| 含义 | 未定义(变量未被赋值) | 空值(主动设置为空) |
| 类型 | undefined |
null |
JavaScript typeof |
"undefined" |
"object"(遗留 Bug) |
| 默认参数触发 | ✅ 会触发默认值 | ❌ 不会触发,会被当作 null 传递 |
| 可选属性/参数类型 | 自动包含 undefined |
需要显式添加 null |
?? 的短路条件 |
✅ | ✅ |
| 推荐使用场景 | 表示"缺失"、"未初始化" | 表示"主动清空"、"空对象指针" |
TypeScript 通过严格的类型系统和编译选项,将 null 和 undefined 区分对待,大大提升了代码的安全性。作为开发者,我们需要理解它们各自的设计初衷,并在项目中保持统一的风格,从而写出更健壮的应用程序。