前端JavaScript:NaN、undefined、null详解
|
zhenglin
2026年5月6日 16:42
本文热度 64
|
在 JavaScript 的世界里,有三个特殊的值常常让初学者甚至有经验的开发者感到困惑:undefined、null 和 NaN。它们都在某种程度上表示 “空” 或 “无值”,但在语义、类型系统以及运行时行为上却有着天壤之别。
很多线上 bug 的根源,往往就在于混淆了这三者的区别。比如,你是否曾疑惑过为什么 typeof null 会返回 'object'?为什么 NaN === NaN 会返回 false?为什么 null == undefined 是 true 但 null === undefined 却是 false?
本文将带你深入这三个特殊值的底层逻辑,拆解它们的产生场景、类型特征以及相等性判断的陷阱,帮助你彻底理清这三者的关系,写出更健壮的代码。
一、undefined:系统的 “未定义”
1.1 语义:自然的缺失
undefined 的核心语义是 “未定义” 。它代表的是一种 “自然的缺失”—— 也就是说,当 JavaScript 引擎在找不到值的时候,它会默认用 undefined 来填充。
这不是开发者主动设置的,而是语言本身的默认行为。当一个变量声明了但还没赋值,或者访问了一个对象上不存在的属性,JavaScript 并不会抛出错误,而是返回 undefined 告诉你:“这里应该有个值,但我还没找到。”
1.2 常见的产生场景
undefined 通常出现在以下几种情况:
变量声明但未赋值:这是最常见的场景。
let name;
console.log(name); // undefined
访问不存在的对象属性:
const user = { name: 'John' };
console.log(user.age); // undefined
函数没有返回值:如果一个函数没有 return 语句,它默认返回 undefined。
function doSomething() {
// 没有 return
}
console.log(doSomething()); // undefined
函数参数未传参:
代码高亮:
function greet(name) {
console.log(name); // undefined
}
greet(); // 没传参数
1.3 类型与注意事项
undefined 本身是一个独立的原始数据类型(Undefined Type),它只有一个值,就是 undefined。
使用 typeof 运算符检测 undefined 时,会准确地返回 'undefined':
let a;
console.log(typeof a); // 'undefined'
最佳实践:不要主动给变量赋值为 undefined。因为 undefined 是语言用来表示 “缺失” 的默认值,如果你手动赋值 let a = undefined,会混淆 “本来就没有值” 和 “我故意把它清空了” 这两种语义。如果你想表示清空,应该使用 null。
二、null:开发者的 “空指针”
2.1 语义:主动的清空
与 undefined 相反,null 的核心语义是 “空值” 。它代表的是一种 “主动的清空”。
null 意味着:“我,开发者,明确地告诉引擎,这个变量现在是空的,它不指向任何对象。”
这是一个有意为之的状态。通常我们用它来表示一个变量本来应该是一个对象,但现在暂时没有值。比如,在等待异步请求返回数据之前,我们可以把变量初始化为 null,表示 “数据还没加载好”。
2.2 常见的使用场景
初始化对象变量:
// 表示用户对象目前为空,等待后续赋值
let currentUser = null;
// 登录成功后
currentUser = { id: 1, name: 'John' };
主动释放引用:在一些手动内存管理的场景下,将对象引用置为 null 可以帮助垃圾回收。
let bigData = getBigData();
// 处理完数据
bigData = null; // 释放引用
函数返回 “无结果” :当查询数据库没有找到结果时,返回 null 而不是抛出错误,表示 “找到了,但结果是空的”。
2.3 那个著名的 Bug:typeof null === 'object'
这是 JavaScript 中最广为人知的历史遗留问题。当你使用 typeof 检测 null 时,你会得到:
代码高亮:
console.log(typeof null); // 'object'
这其实是 JavaScript 最初实现时的一个错误。在最初的 JavaScript 引擎中,值是由一个标签和实际数据表示的。对象的标签是 0,而 null 表示空指针,在大多数平台下是空指针的引用也是 0x00,所以它的标签也被误写成了 0,导致 typeof 把它当成了对象。
虽然这个错误已经被所有人知道了,但由于兼容性的原因,ECMAScript 标准一直没有修复它。
因此,永远不要用 typeof 来检测 null ! 正确的检测方式是直接使用严格相等:
if (value === null) {
// 这才是正确的判断方式
}
三、NaN:数字里的 “坏孩子”
3.1 语义:无效的数字
NaN 全称是 Not-a-Number,即 “非数字”。但这并不意味着它的类型不是数字。恰恰相反,NaN 是一个 数值类型 的特殊值。
它的语义是:“这本来应该是一个数字,但是运算失败了,所以我用 NaN 来表示这个无效的结果。”
比如,你试图把一个字符串 "abc" 转换成数字,或者对负数开平方,JavaScript 不会抛出异常,而是返回 NaN 来告诉你:“这次数字运算搞砸了。”
console.log(typeof NaN); // 'number'
// 没错,它的类型是 number!
这是因为 JavaScript 遵循了 IEEE 754 浮点数标准,而 NaN 正是该标准中定义的一个特殊数值,用来表示非法的计算结果。
3.2 最反直觉的特性:传染性与自不等
NaN 有两个极其特殊的性质,也是无数 bug 的来源:
-
传染性:只要你的数学运算中混入了 NaN,那么最终的结果一定是 NaN。它就像病毒一样会传染。
console.log(1 + NaN); // NaN
console.log(2 * NaN); // NaN
console.log(Math.max(1, 2, NaN, 3)); // NaN
2.这意味着,一旦你的计算链中某个环节出错产生了 NaN,它会一路污染到最终结果,而且很难定位到底是哪里出的错。
它不等于任何值,包括它自己:这是最反直觉的一点。
代码高亮:
console.log(NaN === NaN); // false
console.log(NaN == NaN); // false
为什么会这样?因为 IEEE 754 标准规定,
NaN 不与任何值相等,包括它自己。这是为了让你能通过
x !== x 来检测
NaN。
3.3 如何正确检测 NaN?
既然 === 不好使,那我们该怎么检测一个值是不是 NaN 呢?
console.log(isNaN('hello')); // true!因为 'hello' 转数字失败了
console.log(isNaN(undefined)); // true
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('hello')); // false
console.log(Number.isNaN(undefined)); // false
利用自不等特性:这是一个古老的 trick,因为只有 NaN 才会不等于自己。
function myIsNaN(value) {
return value !== value;
}
四、一张表看懂三者的区别
为了让你更直观地对比这三者的区别,我们整理了一张核心特性对比表:

从表中可以清晰地看到,虽然它们在布尔转换中都为 false,但在类型、语义和转换行为上完全不同。
五、相等性判断的迷局
搞清楚了它们各自的定义,接下来最容易踩坑的就是相等性判断了。JavaScript 提供了三种比较方式:==、=== 和 Object.is(),它们对这三个值的处理各不相同。
5.1 == vs ===:null 和 undefined 的暧昧关系
我们都知道 == 会进行类型转换,而 === 不会。对于 null 和 undefined,ECMAScript 标准做了一个特殊的规定:
null == undefined 必须返回 true。
这是因为语言设计者认为,这两者都表示 “无值”,在宽松比较下应该被视为相等。但在严格比较下,它们是不同的。
代码高亮:
console.log(null == undefined); // true
console.log(null === undefined); // false
这就导致了一个非常有用的简写技巧:如果你想同时检查一个变量是不是 null 或者 undefined,你可以直接写:
if (value == null) {
// 这会同时匹配 null 和 undefined
// 等价于 if (value === null || value === undefined)
}
这在实际开发中非常常用,因为很多时候我们并不关心到底是 null 还是 undefined,我们只关心 “这个值是不是空的”。
ES6 引入了 Object.is() 方法,它解决了 === 无法处理 NaN 的问题。
我们来看一下三种比较方式的区别:
| 比较 | == | === | Object.is() |
|---|
null == undefined | true | false | false |
NaN == NaN | false | false | true |
+0 == -0 | true | true | false |
Object.is() 是最严格的相等判断,它不会做任何类型转换,也不会对 NaN 和 -0 做特殊处理。
console.log(Object.is(NaN, NaN)); // true!终于可以正常判断 NaN 了
console.log(Object.is(null, null)); // true
console.log(Object.is(undefined, undefined)); // true
下面的矩阵图展示了在严格相等(===)下,各个特殊值之间的比较结果:

六、实战避坑:那些年我们踩过的雷
6.1 坑 1:滥用 if (!value)
很多人喜欢用 if (!value) 来判断变量是否为空。但这会把所有的假值(Falsy Value)都过滤掉,包括 0、''、false。
// 错误示范
function processAge(age) {
if (!age) {
console.log('年龄为空');
} else {
console.log('处理年龄', age);
}
}
processAge(0); // 错误!0 是合法年龄,但被当成空了
正确做法:明确检查 null 和 undefined。
代码高亮:
if (age == null) {
console.log('年龄为空');
}
6.2 坑 2:JSON 序列化的丢失
当你使用 JSON.stringify 序列化数据时,undefined、NaN 和 Infinity 会被特殊处理:
undefined、函数、Symbol 会被忽略(在对象中)或者变成 null(在数组中)
JSON.stringify({ a: NaN, b: undefined, c: null });
// 结果: "{"a":null,"c":null}"
注意,这里 NaN 和 undefined 都变成了 null!这意味着你序列化之后,就再也分不清原来的是 NaN 还是 undefined 还是 null 了,这在处理后端数据时要格外小心。
6.3 坑 3:默认参数只对 undefined 生效
ES6 的默认参数只有在参数是 undefined 的时候才会触发,null 不会!
function greet(name = 'Guest') {
console.log(name);
}
greet(undefined); // Guest (触发默认值)
greet(null); // null (不触发!因为 null 是一个明确的传值)
这也符合语义:undefined 表示 “我没传这个参数”,而 null 表示 “我传了,就是空”。
七、最佳实践总结
经过上面的分析,我们可以总结出一套最佳实践,帮助你在日常开发中正确使用这三个值:
-
语义优先:
- 让
undefined 处理 “缺失” 的情况,不要手动赋值它。 - 用
null 表示 “主动清空”,当你想表示一个对象变量为空时使用它。
-
判断准则:
- 检测
null:使用 value === null - 检测
undefined:使用 value === undefined 或者 typeof value === 'undefined'(处理未声明变量) - 检测
NaN:使用 Number.isNaN(value),永远不要用全局的 isNaN() - 同时检测两者:使用
value == null 来同时匹配 null 和 undefined,这是一个安全的简写。
-
利用现代语法:
-
使用空值合并运算符 ?? 来处理默认值,它只会在 null/undefined 时生效,不会误伤 0 或 ''。
const count = response.count ?? 0;
使用可选链运算符 ?. 来安全访问属性,避免 Cannot read property of undefined 错误。
结语
undefined、null 和 NaN,这三个看似简单的值,背后却隐藏着 JavaScript 类型系统的设计哲学和历史包袱。
undefined 是系统告诉你 “这里没东西”。null 是你告诉系统 “这里我故意清空了”。NaN 是系统告诉你 “数字运算炸了”。
理解了它们的区别,你就能在日常开发中避开绝大多数与空值相关的 bug,写出更清晰、更健壮的前端代码。
参考资料
- MDN Web Docs. NaN
- MDN Web Docs. undefined
- MDN Web Docs. Object.is()
- MDN Web Docs. 相等比较和相同
- OpenReplay. The Strange Life of NaN in JavaScript
阅读原文
该文章在 2026/5/6 16:42:39 编辑过