WinddSnow

JavaScript-Data-Types-Part2-Null-Undefined-Symbol-BigInt-Object

字数统计: 3.3k阅读时长: 13 min
2026/07/29

第45课:JavaScript 数据类型(下)——nullundefinedsymbolbigintObject

上一节课我们学习了 stringnumberboolean 三种最常用的原始类型。本节课将覆盖剩余的全部内置类型:nullundefined 这一对容易混淆的“空值”、symbol(唯一标识符)、bigint(任意精度整数),以及 JavaScript 中最核心的复合类型——Object 的基础语法。理解这些类型的语义、行为差异和适用场景,是写出健壮、无 Bug 代码的必修课。


1. nullundefined:两种“空”的语义

nullundefined 都表示“没有值”,但它们的语义和使用场景截然不同。混淆它们是 JavaScript 中许多隐蔽错误的根源。

1.1 undefined:值尚未定义

undefined 是 JavaScript 引擎在以下情况自动赋予的值:

场景 示例 结果
变量声明但未赋值 let x; xundefined
函数未显式 return function f() {} f() 返回 undefined
函数调用时未传入的形参 function f(a) {} 调用 f() aundefined
访问对象不存在的属性 const obj = {}; obj.name undefined
void 运算符的返回值 void 0 undefined
1
2
3
4
5
6
7
let age;
console.log(age); // undefined —— 声明未赋值

function greet(name) {
console.log('Hello, ' + name);
}
console.log(greet('Alice')); // 先打印 'Hello, Alice',然后打印 undefined(函数无 return)

语义undefined 表示“尚未赋值”或“不存在”。这是系统层面的空缺,通常不由程序员主动赋值(尽管技术上可以)。

1.2 null:有意的空值

null 是程序员主动赋值的空值,表示“此处故意没有值”或“对象引用为空”。

1
2
3
let user = null; // 明确表示当前没有用户对象
// ...后续逻辑
user = { name: 'Alice' }; // 赋值对象

典型使用场景

  • 初始化一个变量,表示稍后会被赋予对象。
  • 函数返回一个对象,若不存在则返回 null(而非 undefined)。
  • 原型链的终点:Object.prototype.__proto__ === null
1
2
3
4
function findUser(id) {
if (id === 1) return { name: 'Alice' };
return null; // 明确表示“未找到”
}

1.3 关键差异对比

特性 undefined null
typeof 结果 'undefined' 'object'历史遗留 Bug
产生方式 系统自动赋予。 程序员显式赋予。
语义 尚未赋值、属性不存在、无返回值。 明确表示“空”或“没有对象”。
数值转换 Number(undefined)NaN Number(null)0
== 比较 null == undefinedtrue(特殊规则) null === undefinedfalse

typeof null === 'object' 是 JavaScript 的一个古老 Bug(始于第一版,无法修复以免破坏现有代码)。判断 null 的正确方式是严格相等:

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

1.4 判断“空值”的最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 同时检查 null 和 undefined(利用了 == 的特殊规则)
if (value == null) {
// value 是 null 或 undefined
}

// 严格区分
if (value === null) { /* 仅 null */ }
if (value === undefined) { /* 仅 undefined */ }

// 函数参数默认值:只有 undefined 会触发默认值,null 不会
function greet(name = 'Guest') {
console.log(name);
}
greet(undefined); // 'Guest'
greet(null); // null(null 是有效值,不触发默认值)

2. symbol:唯一标识符

symbol 是 ES6 引入的原始类型,用于创建全局唯一的值。每一个 Symbol() 调用返回的值都与所有其他值不同,即使传入相同的描述字符串。

2.1 创建 Symbol

1
2
3
4
const s1 = Symbol('desc');
const s2 = Symbol('desc');
console.log(s1 === s2); // false —— 每个 Symbol 都是唯一的
console.log(s1.description); // 'desc'

Symbol() 不能使用 new 调用(会抛出 TypeError)。传入的字符串是可选的描述符,仅用于调试。

2.2 Symbol 作为对象属性键

Symbol 的主要用途是作为对象的属性键,防止属性名冲突

1
2
3
4
5
6
7
8
const id = Symbol('id');
const user = {
name: 'Alice',
[id]: 12345, // 使用计算属性名语法
};

console.log(user[id]); // 12345
console.log(user.id); // undefined —— 点号无法访问 Symbol 键

Symbol 属性的关键行为

  • Object.keys(user) 不会返回 Symbol 键。
  • for...in 循环不会遍历 Symbol 键。
  • Object.getOwnPropertySymbols(user) 可获取 Symbol 键数组。
  • JSON.stringify()忽略 Symbol 键。

这种“半隐藏”特性使得 Symbol 非常适合存储元数据库内部状态,而不会与应用代码的普通属性冲突。

2.3 全局共享的 Symbol:Symbol.for()

Symbol.for(key)全局 Symbol 注册表中查找或创建一个与 key 关联的 Symbol。相同的 key 总是返回同一个 Symbol。

1
2
3
4
5
const globalSym1 = Symbol.for('app.id');
const globalSym2 = Symbol.for('app.id');
console.log(globalSym1 === globalSym2); // true —— 全局唯一

console.log(Symbol.keyFor(globalSym1)); // 'app.id'

Symbol()Symbol.for() 的区别

  • Symbol('desc') 创建的是局部唯一值,永远不会相等。
  • Symbol.for('key') 创建的是全局共享值,相同 key 返回同一 Symbol。

2.4 内置的知名 Symbol(Well-Known Symbols)

JavaScript 预定义了一些 Symbol 值,用于自定义语言内部行为。它们作为 Symbol 构造函数的静态属性。

知名 Symbol 用途
Symbol.iterator 定义对象的默认迭代器(用于 for...of)。
Symbol.toStringTag 定义 Object.prototype.toString() 返回的类型标签。
Symbol.toPrimitive 定义对象转换为原始值时的行为。
Symbol.hasInstance 定义 instanceof 的行为。
Symbol.species 定义派生对象(如 Array.map 返回的数组)的构造函数。
1
2
3
4
5
// 自定义 toStringTag
const myObj = {
[Symbol.toStringTag]: 'MyCustomType',
};
console.log(Object.prototype.toString.call(myObj)); // '[object MyCustomType]'

3. bigint:任意精度整数

bigint 是 ES2020 引入的原始类型,用于表示任意大的整数,突破了 number 的安全整数范围限制(±2^53 - 1)。

3.1 创建 BigInt

1
2
3
4
5
6
7
const big1 = 9007199254740992n;           // 数字字面量 + n 后缀
const big2 = BigInt('9007199254740992'); // BigInt 函数
const big3 = BigInt(123); // 从 number 转换(仅限安全整数)

// 不能从带小数的数值或字符串创建
// BigInt(1.5); // RangeError
// BigInt('1.5'); // SyntaxError

3.2 运算与限制

BigInt 支持 +-*/%** 以及位运算,但除法会截断小数部分(向零取整)。

1
2
3
4
5
6
7
const a = 10n;
const b = 3n;
console.log(a + b); // 13n
console.log(a / b); // 3n(不是 3.333...)

// 取模
console.log(10n % 3n); // 1n

关键限制

  • 不能与 number 混用BigIntnumber 不能直接进行算术运算,会抛出 TypeError。必须显式转换。
  • Math 对象的方法不接受 BigIntMath.max(1n, 2n) 会报错。
  • 不能与 number 混合使用 for 循环索引
  • JSON.stringify 不支持 BigInt,会抛出 TypeError
1
2
3
4
5
6
7
const big = 10n;
const num = 5;
// console.log(big + num); // TypeError: Cannot mix BigInt and other types

// 必须显式转换
console.log(big + BigInt(num)); // 15n
console.log(Number(big) + num); // 15(注意:若 BigInt 超出安全范围会丢失精度)

3.3 比较与转换

BigInt 可以与 number 进行关系比较><>=<=),但不能使用 ==(松散相等可以,但不推荐)。

1
2
3
console.log(1n < 2);   // true(关系比较允许混用)
console.log(1n == 1); // true(松散相等,但不推荐)
console.log(1n === 1); // false(严格相等,类型不同)

转换

  • BigInt(value)numberstring 转为 BigInt。
  • Number(bigint) 将 BigInt 转为 number(可能丢失精度)。

4. Object 基础:键值对的集合

object 是 JavaScript 中唯一的可变类型,也是所有非原始类型的基类。对象是属性的无序集合,每个属性都是一个键值对。

4.1 对象字面量语法

1
2
3
4
5
const user = {
name: 'Alice', // 属性名:属性值
age: 25,
'favorite color': 'blue', // 属性名包含空格时必须用引号
};

4.2 属性访问:点号与方括号

语法 示例 适用场景 限制
点号 . user.name 属性名是合法的标识符(无空格、不以数字开头等)。 不能访问动态键名、不能访问数字开头的属性。
方括号 [] user['favorite color'] 属性名包含特殊字符、动态键名、变量键名。 无限制。
1
2
3
const key = 'age';
console.log(user[key]); // 25 —— 变量键名只能用方括号
console.log(user.key); // undefined —— 点号访问的是字面量 'key' 属性

4.3 ES6 增强的对象字面量

属性值简写:当属性名与变量名相同时,可省略冒号和值。

1
2
3
const name = 'Bob';
const age = 30;
const user = { name, age }; // 等价于 { name: name, age: age }

方法简写:省略 function 关键字和冒号。

1
2
3
4
const obj = {
greet() { console.log('Hello'); } // 等价于 greet: function() { ... }
};
obj.greet();

计算属性名:用 [] 包裹表达式,动态生成属性名。

1
2
3
4
5
6
const propName = 'status';
const task = {
[propName]: 'done', // 属性名是变量 propName 的值 'status'
['time' + 'stamp']: Date.now(), // 属性名是 'timestamp'
};
console.log(task); // { status: 'done', timestamp: 1735478400000 }

4.4 属性存在性检查:in 操作符

in 检查属性是否存在于对象自身或原型链中。与 hasOwnProperty 不同,后者只检查自身属性。

1
2
3
4
5
const user = { name: 'Alice' };

console.log('name' in user); // true
console.log('toString' in user); // true(继承自 Object.prototype)
console.log(user.hasOwnProperty('toString')); // false(仅自身属性)

判断属性值是否为 undefined 不可靠:因为属性可能显式设置为 undefined

1
2
3
const obj = { key: undefined };
console.log(obj.key === undefined); // true —— 但无法区分“不存在”还是“值为 undefined”
console.log('key' in obj); // true —— 属性存在

4.5 遍历对象属性

方法 遍历范围 包含 Symbol 包含不可枚举
for...in 自身 + 原型链可枚举属性
Object.keys(obj) 自身可枚举属性
Object.getOwnPropertyNames(obj) 自身所有非 Symbol 属性
Object.getOwnPropertySymbols(obj) 自身所有 Symbol 属性
1
2
3
4
5
6
7
8
9
10
11
const sym = Symbol('id');
const obj = Object.create({ inherited: true }); // 原型属性
obj.own = 1;
obj[sym] = 2;

for (const key in obj) {
console.log(key); // 'own', 'inherited'
}
console.log(Object.keys(obj)); // ['own']
console.log(Object.getOwnPropertyNames(obj)); // ['own']
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id)]

推荐:除非你明确需要遍历原型属性(极罕见),否则使用 Object.keys() 遍历自身属性。


课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) typeof null 的结果是?
    A. 'null'
    B. 'undefined'
    C. 'object'
    D. 'boolean'

  2. (单选) 以下关于 Symbol 的描述,正确的是?
    A. Symbol('a') === Symbol('a') 返回 true
    B. Symbol.for('a') === Symbol.for('a') 返回 true
    C. for...in 循环会遍历 Symbol 属性。
    D. JSON.stringify 会包含 Symbol 属性。

  3. (填空) BigInt 字面量需要在数字后面加上 ______ 后缀,例如 42n

  4. (多选) 以下哪些方法可以获取对象自身的属性(不包括原型链上的属性)?
    A. for...in
    B. Object.keys()
    C. Object.getOwnPropertyNames()
    D. in 操作符

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要编写一个 JavaScript 工具函数库,包含以下两个函数:

  • deepClone(obj):使用递归方式实现对象的深拷贝。要求能正确处理嵌套的普通对象和数组,并保留对象中的 Symbol 属性键。函数需要处理 null 和基本类型(直接返回)。使用 Object.getOwnPropertyDescriptors 或手动遍历 Symbol 键确保 Symbol 属性也被拷贝。
  • isPlainObject(value):判断一个值是否为普通对象(即 {}new Object() 创建的对象,排除 null、数组、函数、Date 等)。使用 Object.prototype.toString.call(value) 判断,返回 '[object Object]' 时为 true

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 JavaScript 代码。提示词中需明确指定处理 Symbol 属性的方式、typeofnull 的判断、深拷贝的递归逻辑以及 isPlainObject 的判断标准。


课后练习答案

一、概念自测答案

  1. C

    • 解析:typeof null === 'object' 是 JavaScript 的历史遗留 Bug。
  2. B

    • 解析:Symbol.for(key) 基于全局注册表,相同 key 返回同一个 Symbol。A 错误,每次 Symbol() 调用都创建新 Symbol;C 错误,for...in 跳过 Symbol 键;D 错误,JSON.stringify 忽略 Symbol 属性。
  3. n

    • 解析:BigInt 字面量以 n 结尾,如 10n0xFFn
  4. B、C

    • 解析:Object.keys() 返回自身可枚举非 Symbol 属性;Object.getOwnPropertyNames() 返回自身所有非 Symbol 属性(包括不可枚举)。A 和 D 都会包含原型属性。

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请编写一个 JavaScript 工具模块,包含两个导出函数 deepCloneisPlainObject。要求:

  • deepClone(obj)
    • objnull 或非对象(typeof obj !== 'object'),直接返回 obj
    • 若是数组,创建新数组并递归拷贝每个元素。
    • 若是普通对象,创建新对象,使用 Object.getOwnPropertyNames(obj)Object.getOwnPropertySymbols(obj) 获取所有自身键(包括 Symbol),并用 Object.getOwnPropertyDescriptor 获取属性描述符,通过 Object.defineProperty 复制到新对象,值递归调用 deepClone。注意处理循环引用(简单处理:若检测到循环引用可抛出错误或使用 WeakMap 记录)。
    • 特殊对象(如 Date、RegExp、Map、Set 等)暂不做特殊处理,按普通对象拷贝(提示中注明生产环境需扩展)。
  • isPlainObject(value)
    • 使用 Object.prototype.toString.call(value) 判断,若返回 '[object Object]' 则为 true,否则 false
  • 使用 const 声明函数,变量命名遵循 camelCase。添加 JSDoc 注释。
  • 提供使用示例:深拷贝一个包含 Symbol 属性和嵌套对象的对象,以及 isPlainObject 对不同类型值的测试。
  • 代码可直接运行。输出完整代码。”
CATALOG
  1. 1. 第45课:JavaScript 数据类型(下)——null、undefined、symbol、bigint、Object
    1. 1.1. 1. null 与 undefined:两种“空”的语义
      1. 1.1.1. 1.1 undefined:值尚未定义
      2. 1.1.2. 1.2 null:有意的空值
      3. 1.1.3. 1.3 关键差异对比
      4. 1.1.4. 1.4 判断“空值”的最佳实践
    2. 1.2. 2. symbol:唯一标识符
      1. 1.2.1. 2.1 创建 Symbol
      2. 1.2.2. 2.2 Symbol 作为对象属性键
      3. 1.2.3. 2.3 全局共享的 Symbol:Symbol.for()
      4. 1.2.4. 2.4 内置的知名 Symbol(Well-Known Symbols)
    3. 1.3. 3. bigint:任意精度整数
      1. 1.3.1. 3.1 创建 BigInt
      2. 1.3.2. 3.2 运算与限制
      3. 1.3.3. 3.3 比较与转换
    4. 1.4. 4. Object 基础:键值对的集合
      1. 1.4.1. 4.1 对象字面量语法
      2. 1.4.2. 4.2 属性访问:点号与方括号
      3. 1.4.3. 4.3 ES6 增强的对象字面量
      4. 1.4.4. 4.4 属性存在性检查:in 操作符
      5. 1.4.5. 4.5 遍历对象属性
    5. 1.5. 课后练习
      1. 1.5.1. 一、概念自测(选择题 / 填空题)
      2. 1.5.2. 二、AI 编程任务:编写面向 AI 的提示词
    6. 1.6. 课后练习答案
      1. 1.6.1. 一、概念自测答案
      2. 1.6.2. 二、AI 编程任务参考答案(提示词示例)