JS 中 undefined 和 null 的区别不只是类型不同

FreeGuideOnline 最新 2026-07-04

一、表面印象:类型上的差异

初学 JavaScript 时,很多人通过 typeof 操作符记住两者的类型区别:

console.log(typeof undefined); // "undefined"
console.log(typeof null);      // "object"

undefined 的类型就是 "undefined",而 null 的类型竟然是 "object" —— 这是 JS 语言早期的一个著名 bug,并且因为兼容性问题被永久保留。
但这仅仅是区别的开始,更深层的差异藏在语义、使用场景、隐式转换、相等性比较等多个维度中。


二、语义层面:缺省值 vs 空对象引用

2.1 undefined 表示“还没有值”

undefined 是 JavaScript 引擎自动赋予的“缺省状态”,通常出现在以下情况:

  • 变量已声明但未赋值
    let a;
    console.log(a); // undefined
    
  • 函数参数未传递实参
    function fn(x) { console.log(x); }
    fn(); // undefined
    
  • 函数没有显式返回值
    function noReturn() {}
    console.log(noReturn()); // undefined
    
  • 访问对象中不存在的属性
    const obj = { name: 'JS' };
    console.log(obj.age); // undefined
    

undefined 像是一个天然的“空位”标记:声明了空间,但内容尚未填入

2.2 null 表示“有意的空值”

null 则无法由引擎自动生成,必须由程序员明确赋值。它的语义是:

此处应该有一个对象,但目前明确不想让它指向任何有效对象。

常见场景包括:

  • 初始化一个将来要赋值为对象的变量
    let user = null; // 稍后从服务器获取用户对象
    
  • 清空一个对象的引用,协助垃圾回收
    let largeData = getLargeData();
    // 使用完毕后
    largeData = null;
    
  • 函数需要返回对象,但某些情况下无有效对象可返回
    function findUser(id) {
      if (id > 0) return { id, name: 'test' };
      return null; // 明确表示没有找到
    }
    

可以看出,null 是一个主动的、程序意图明确的“空对象值”。


三、历史与类型标记:为什么 typeof null === "object"

这是理解两者区别时绕不开的话题。
在 JavaScript 最初的实现中,值以二进制形式存储,并用低位的类型标签来区分。对象的类型标签是 000,而 null 被设计为全零的指针,即所有位都是 0,因此它的类型标签恰好也是 000,于是 typeof 错误地将其报告为 "object"

该行为被写入 ECMAScript 规范,并注明为历史遗留问题,无法修复。
这也提醒我们:不要依赖 typeof 来检测 null,应使用严格相等:

if (value === null) { /* ... */ }

四、相等性与比较:== 与 === 的天壤之别

4.1 宽松相等 == 的比较特例

undefinednull== 比较中存在一条极为特殊的规则:

在抽象相等比较算法中,nullundefined 互相宽松相等,且与自身宽松相等,但不与其他任何值宽松相等。

console.log(null == undefined);      // true
console.log(undefined == null);      // true
console.log(null == null);           // true
console.log(undefined == undefined); // true

console.log(null == false);          // false
console.log(null == 0);              // false
console.log(null == '');             // false
console.log(undefined == false);     // false
console.log(undefined == 0);         // false
console.log(undefined == '');        // false

这一规则意味着:nullundefined 在宽松比较中形成了一个独立的等价组,它们之间可以互相“视为相等”,但这些却和 false0"" 这些常见的“假值”无关。

4.2 严格相等 === 的类型区分

console.log(null === undefined); // false
console.log(null === null);      // true
console.log(undefined === undefined); // true

严格相等要求类型完全相同,因此 nullundefined 永远不严格相等。
最佳实践:除非你有意利用 == 的规则一次性过滤 nullundefined,否则始终使用 === 以避免意料之外的类型转换。


五、数值转换:null 成功转为 0,undefined 变为 NaN

当参与数学运算或强制转换为数字时,两者行为差异极大:

Number(null);      // 0
Number(undefined); // NaN

5 + null;          // 5  (null -> 0)
5 + undefined;     // NaN
  • null 转换为数字 0,这符合“空对象指针”被量化为零的逻辑。
  • undefined 转换结果为 NaN,因为它表示“没有值”,无法产生有意义的数字。

这种差异在涉及默认数值累加的场景中容易造成隐蔽 bug:

let total = 0;
function add(value) {
  total += value; // 如果意外传入 undefined,total 将变成 NaN
}

防御性编程中,经常会见到对 undefined 的格外处理:

total += value ?? 0; // 使用 nullish 合并运算符

六、JSON 序列化:null 被保留,undefined 被移除

在处理 JSON 时,两者的表现截然不同:

const data = {
  a: null,
  b: undefined,
  c: function() {},
  d: Symbol('id')
};
console.log(JSON.stringify(data));
// {"a":null}
  • null 是合法的 JSON 值,会被原样序列化。
  • undefined、函数、Symbol 类型的属性会在序列化时被直接忽略
  • 数组中的 undefined 会被转换成 null
    JSON.stringify([1, undefined, 3]); // [1,null,3]
    

这一区别直接影响了 API 设计:
如果某个字段需要明确表示“无值”并能通过 JSON 传输,应使用 null;如果只是内部临时空位,可以用 undefined


七、函数参数默认值与 arguments

7.1 参数默认值的行为

ES6 引入了参数默认值,它的触发条件与 undefined 紧密相关:

function greet(name = 'Guest') {
  console.log(name);
}
greet();          // 'Guest' (未传参数 => undefined 触发默认值)
greet(undefined); // 'Guest' (显式传入 undefined 也能触发默认值)
greet(null);      // null       (null 作为有效值传入,不触发默认值)
  • 传入 undefined(包括未传参)会使用默认值。
  • 传入 null 则被视为一个有效值,不会触发默认值。

这一设计思路再一次印证:
undefined = 缺失 / 未提供,null = 明确提供的空值。

7.2 在 arguments 对象中的表现

非严格模式下,修改形参会反映到 arguments 中,但 nullundefined 均作为独立的值传入:

function test(a) {
  arguments[0] = 'changed';
  console.log(a, arguments[0]);
}
test(undefined); // changed changed  (a 与 arguments 同步)
test(null);      // changed changed

这本身不是两者间的直接区别,但通过参数默认值能看到它们语义上的分工。


八、可选链与空值合并:现代 JavaScript 中的“空值”处理

ES2020 引入的可选链 ?. 和空值合并运算符 ??,为处理 nullundefined 提供了统一而优雅的方式,同时也反过来展示了它们共同属于“空值”这一特殊范畴。

const user = null;
console.log(user?.name); // undefined,不会报错

console.log(user ?? 'Anonymous'); // 'Anonymous'
console.log(0 ?? 'default');      // 0  (0 不是 null/undefined)

?? 只对 nullundefined 取右侧备用值,这与 || 的“所有假值”逻辑完全不同:

console.log(0 || 'default');   // 'default'
console.log('' || 'default');  // 'default'
console.log(false || 'default'); // 'default'

这进一步强调:nullundefined 是 JavaScript 中仅有被认定为“无值”的两类值,其他假值(0""falseNaN)都是有意义的值。


九、实战总结:何时使用哪种“空”

场景 推荐使用 理由
变量声明但暂时无法初始化 undefined 或不赋值 自动获得,语义为“还没准备好”
对象或值的“占位” null 明确表示“应该有一个对象但故意为空”
函数参数可选且可能需要重置 检查 undefined 参数默认值只认 undefined
清除引用,帮助垃圾回收 null 将变量设为 null 可以释放引用
作为 JSON 数据中的空值 null undefined 会被序列化忽略
?? 提供默认值 都可以 ?? 对两者一视同仁
== 同时检查两者 同时检查 只在需要“有无值”而不区分的场景下使用,一般不建议滥用

十、要点速记

  1. 类型typeof undefined"undefined"typeof null"object"(历史 bug)。
  2. 来源undefined 由引擎自动赋予表示“缺失”;null 由开发者主动赋予表示“无对象”。
  3. 相等性null == undefinedtrue,与其他任何值均不宽松相等;=== 则严格区分。
  4. 数字转换Number(null)0Number(undefined)NaN
  5. JSON 序列化null 保留,undefined 被忽略(对象属性中)或转为 null(数组中)。
  6. 参数默认值:仅 undefined 触发默认值,null 作为有效值传入。
  7. 现代操作符???. 平等对待 nullundefined,两者合称“空值”。

理解 nullundefined 远超记住类型标签,它关乎你如何设计代码的“空值哲学”:
undefined 表示“本不应该有值”,用 null 表示“故意让它暂无值”。