第45课:JavaScript 数据类型(下)——null、undefined、symbol、bigint、Object
上一节课我们学习了 string、number 和 boolean 三种最常用的原始类型。本节课将覆盖剩余的全部内置类型:null 和 undefined 这一对容易混淆的“空值”、symbol(唯一标识符)、bigint(任意精度整数),以及 JavaScript 中最核心的复合类型——Object 的基础语法。理解这些类型的语义、行为差异和适用场景,是写出健壮、无 Bug 代码的必修课。
1. null 与 undefined:两种“空”的语义
null 和 undefined 都表示“没有值”,但它们的语义和使用场景截然不同。混淆它们是 JavaScript 中许多隐蔽错误的根源。
1.1 undefined:值尚未定义
undefined 是 JavaScript 引擎在以下情况自动赋予的值:
| 场景 | 示例 | 结果 |
|---|---|---|
| 变量声明但未赋值 | let x; |
x 为 undefined |
函数未显式 return |
function f() {} |
f() 返回 undefined |
| 函数调用时未传入的形参 | function f(a) {} 调用 f() |
a 为 undefined |
| 访问对象不存在的属性 | const obj = {}; obj.name |
undefined |
void 运算符的返回值 |
void 0 |
undefined |
1 | let age; |
语义:undefined 表示“尚未赋值”或“不存在”。这是系统层面的空缺,通常不由程序员主动赋值(尽管技术上可以)。
1.2 null:有意的空值
null 是程序员主动赋值的空值,表示“此处故意没有值”或“对象引用为空”。
1 | let user = null; // 明确表示当前没有用户对象 |
典型使用场景:
- 初始化一个变量,表示稍后会被赋予对象。
- 函数返回一个对象,若不存在则返回
null(而非undefined)。 - 原型链的终点:
Object.prototype.__proto__ === null。
1 | function findUser(id) { |
1.3 关键差异对比
| 特性 | undefined |
null |
|---|---|---|
typeof 结果 |
'undefined' |
'object'(历史遗留 Bug) |
| 产生方式 | 系统自动赋予。 | 程序员显式赋予。 |
| 语义 | 尚未赋值、属性不存在、无返回值。 | 明确表示“空”或“没有对象”。 |
| 数值转换 | Number(undefined) → NaN |
Number(null) → 0 |
| == 比较 | null == undefined → true(特殊规则) |
null === undefined → false |
typeof null === 'object' 是 JavaScript 的一个古老 Bug(始于第一版,无法修复以免破坏现有代码)。判断 null 的正确方式是严格相等:
1 | if (value === null) { /* ... */ } |
1.4 判断“空值”的最佳实践
1 | // 同时检查 null 和 undefined(利用了 == 的特殊规则) |
2. symbol:唯一标识符
symbol 是 ES6 引入的原始类型,用于创建全局唯一的值。每一个 Symbol() 调用返回的值都与所有其他值不同,即使传入相同的描述字符串。
2.1 创建 Symbol
1 | const s1 = Symbol('desc'); |
Symbol() 不能使用 new 调用(会抛出 TypeError)。传入的字符串是可选的描述符,仅用于调试。
2.2 Symbol 作为对象属性键
Symbol 的主要用途是作为对象的属性键,防止属性名冲突。
1 | const id = Symbol('id'); |
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 | const globalSym1 = Symbol.for('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 | // 自定义 toStringTag |
3. bigint:任意精度整数
bigint 是 ES2020 引入的原始类型,用于表示任意大的整数,突破了 number 的安全整数范围限制(±2^53 - 1)。
3.1 创建 BigInt
1 | const big1 = 9007199254740992n; // 数字字面量 + n 后缀 |
3.2 运算与限制
BigInt 支持 +、-、*、/、%、** 以及位运算,但除法会截断小数部分(向零取整)。
1 | const a = 10n; |
关键限制:
- 不能与
number混用:BigInt和number不能直接进行算术运算,会抛出TypeError。必须显式转换。 - Math 对象的方法不接受 BigInt:
Math.max(1n, 2n)会报错。 - 不能与
number混合使用for循环索引。 JSON.stringify不支持 BigInt,会抛出TypeError。
1 | const big = 10n; |
3.3 比较与转换
BigInt 可以与 number 进行关系比较(>、<、>=、<=),但不能使用 ==(松散相等可以,但不推荐)。
1 | console.log(1n < 2); // true(关系比较允许混用) |
转换:
BigInt(value)将number或string转为 BigInt。Number(bigint)将 BigInt 转为 number(可能丢失精度)。
4. Object 基础:键值对的集合
object 是 JavaScript 中唯一的可变类型,也是所有非原始类型的基类。对象是属性的无序集合,每个属性都是一个键值对。
4.1 对象字面量语法
1 | const user = { |
4.2 属性访问:点号与方括号
| 语法 | 示例 | 适用场景 | 限制 |
|---|---|---|---|
点号 . |
user.name |
属性名是合法的标识符(无空格、不以数字开头等)。 | 不能访问动态键名、不能访问数字开头的属性。 |
方括号 [] |
user['favorite color'] |
属性名包含特殊字符、动态键名、变量键名。 | 无限制。 |
1 | const key = 'age'; |
4.3 ES6 增强的对象字面量
属性值简写:当属性名与变量名相同时,可省略冒号和值。
1 | const name = 'Bob'; |
方法简写:省略 function 关键字和冒号。
1 | const obj = { |
计算属性名:用 [] 包裹表达式,动态生成属性名。
1 | const propName = 'status'; |
4.4 属性存在性检查:in 操作符
in 检查属性是否存在于对象自身或原型链中。与 hasOwnProperty 不同,后者只检查自身属性。
1 | const user = { name: 'Alice' }; |
判断属性值是否为 undefined 不可靠:因为属性可能显式设置为 undefined。
1 | const obj = { key: undefined }; |
4.5 遍历对象属性
| 方法 | 遍历范围 | 包含 Symbol | 包含不可枚举 |
|---|---|---|---|
for...in |
自身 + 原型链可枚举属性 | 否 | 否 |
Object.keys(obj) |
自身可枚举属性 | 否 | 否 |
Object.getOwnPropertyNames(obj) |
自身所有非 Symbol 属性 | 否 | 是 |
Object.getOwnPropertySymbols(obj) |
自身所有 Symbol 属性 | 是 | 是 |
1 | const sym = Symbol('id'); |
推荐:除非你明确需要遍历原型属性(极罕见),否则使用 Object.keys() 遍历自身属性。
课后练习
一、概念自测(选择题 / 填空题)
(单选)
typeof null的结果是?
A.'null'
B.'undefined'
C.'object'
D.'boolean'(单选) 以下关于
Symbol的描述,正确的是?
A.Symbol('a') === Symbol('a')返回true。
B.Symbol.for('a') === Symbol.for('a')返回true。
C.for...in循环会遍历 Symbol 属性。
D.JSON.stringify会包含 Symbol 属性。(填空) BigInt 字面量需要在数字后面加上
______后缀,例如42n。(多选) 以下哪些方法可以获取对象自身的属性(不包括原型链上的属性)?
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 属性的方式、typeof 对 null 的判断、深拷贝的递归逻辑以及 isPlainObject 的判断标准。
课后练习答案
一、概念自测答案
C
- 解析:
typeof null === 'object'是 JavaScript 的历史遗留 Bug。
- 解析:
B
- 解析:
Symbol.for(key)基于全局注册表,相同 key 返回同一个 Symbol。A 错误,每次Symbol()调用都创建新 Symbol;C 错误,for...in跳过 Symbol 键;D 错误,JSON.stringify忽略 Symbol 属性。
- 解析:
n- 解析:BigInt 字面量以
n结尾,如10n、0xFFn。
- 解析:BigInt 字面量以
B、C
- 解析:
Object.keys()返回自身可枚举非 Symbol 属性;Object.getOwnPropertyNames()返回自身所有非 Symbol 属性(包括不可枚举)。A 和 D 都会包含原型属性。
- 解析:
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请编写一个 JavaScript 工具模块,包含两个导出函数deepClone和isPlainObject。要求:
deepClone(obj):
- 若
obj为null或非对象(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对不同类型值的测试。- 代码可直接运行。输出完整代码。”