JavaScript 数据类型深度解析 —— 从null到浮点数精度
一篇来自代码实践的学习笔记
一、null —— 不仅仅是"空"
let a = null;
let b = a;
b = 2;
console.log(a, b); // null, 2
null 是 JavaScript 的原始类型(primitive)之一。原始类型存储在栈内存中,空间固定,赋值时采用的是值拷贝——b 拿到的是 null 这个值的独立副本,修改 b 不会影响 a。
null 的语义是"已声明但没有值" ,通常由开发者主动赋值,表示"这里应该为空"。
二、原始类型 vs 引用类型 —— 一道重要的分水岭
let obj = { name: "2222" };
let obj2 = obj; // 引用式赋值
obj2.company = "字节跳动";
console.log(obj, obj2); // 两者都有 company 属性!
对象(包括数组、函数)是引用类型,存储在堆内存中。赋值时拷贝的是内存地址的引用,而不是值本身。obj 和 obj2 指向同一个对象,所以修改 obj2.company 也会反映在 obj 上。
| 原始类型 | 引用类型 | |
|---|---|---|
| 存储位置 | 栈内存 | 堆内存 |
| 赋值方式 | 拷贝值 | 拷贝引用 |
| 类型 | null, undefined, number, string, boolean, symbol, bigint | object, array, function |
| 修改独立性 | 互不影响 | 相互影响 |
三、null 与 undefined —— 看似相同,实则不同
let obj3 = {
name: "1111",
address: null
};
console.log(obj3.address); // null —— 主动设置为空
console.log(obj3.age); // undefined —— 属性根本不存在
null | undefined | |
|---|---|---|
| 含义 | 空值(人为赋予) | 未定义(系统默认) |
| 谁设置的 | 开发者主动赋值 | JS引擎自动赋予 |
| 典型场景 | 清空变量、重置引用 | 变量未初始化、属性不存在、函数无返回值 |
typeof | "object"(历史遗留bug) | "undefined" |
一个实用的判断技巧:
// 如果想区分"未定义"和"主动置空"
if (value === undefined) { /* 属性不存在 */ }
if (value === null) { /* 开发者主动清空 */ }
if (value == null) { /* 同时匹配 null 和 undefined */ }
四、手动内存回收 —— 给 GC 一个暗示
let largeObject = {
data: new Array(100000000).fill("hgh")
};
// 不再使用时:
largeObject = null;
JavaScript 有自动的垃圾回收机制(Garbage Collection),但将大对象主动置为 null,可以切断引用链,让 GC 更早地识别并回收这块内存。这在以下场景尤为重要:
- 持有大数组 / 大对象且不再需要时
- 闭包中引用了不再需要的变量
- 全局变量引用了大量数据
⚠️ 注意:这只是一个"提示",不是强制回收命令。最终回收时机由 JS 引擎决定。
五、浮点数精度 —— JavaScript 的经典"坑"
let a = 0.1;
let b = 0.2;
console.log(a + b); // 0.30000000000000004 ← 不是 0.3!
为什么? JavaScript 使用 IEEE 754 双精度浮点数(64位二进制) 来存储所有数字。0.1 和 0.2 在二进制中是无限循环小数,就像十进制的 1/3 = 0.333... 一样无法精确表示。存储时被截断,相加后再转换回十进制时,误差就显现了。
解决方案:
// 方法1:转为整数运算
(0.1 * 10 + 0.2 * 10) / 10; // 0.3
// 方法2:限制小数位数
(0.1 + 0.2).toFixed(2); // "0.30"
// 方法3:使用极小误差阈值比较
Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true
// 方法4:借助第三方库(decimal.js、big.js 等)
六、超大整数 —— 又一个精度陷阱
let num1 = 9999999999999999999999999999999999999...;
let num2 = 1234567890987654334673245776547890087...;
console.log(num1 + num2); // 结果可能不是你想要的
JavaScript 的 number 类型安全整数范围是:
Number.MAX_SAFE_INTEGER; // 9007199254740991 (2^53 - 1)
Number.MIN_SAFE_INTEGER; // -9007199254740991
超过这个范围的整数会丢失精度。此时应使用 BigInt:
let num1 = 9999999999999999999999999999999999999n; // 注意末尾的 n
let num2 = 1234567890987654334673245776547890087n;
console.log(num1 + num2); // 精确结果
总结
这组代码涵盖了 JavaScript 类型系统的几个核心知识点:
JS 类型体系
├── 原始类型 (Primitive)
│ ├── null → 主动置空、释放引用
│ ├── undefined → 系统默认"未定义"
│ ├── number → IEEE 754,小心精度!
│ ├── bigint → 超大整数的救星
│ ├── string
│ ├── boolean
│ └── symbol
└── 引用类型 (Reference)
└── object / array / function → 拷贝引用,修改共享
理解这些底层机制,能帮你避免写出一堆"玄学 bug"。记住三件事:
- 赋值时想清楚是值拷贝还是引用拷贝
- 浮点数运算永远不要直接
===比较 - 大对象不用了记得断引用(
= null)