JavaScript 数据类型深度解析 —— 从null到浮点数精度

0 阅读4分钟

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 属性!

对象(包括数组、函数)是引用类型,存储在堆内存中。赋值时拷贝的是内存地址的引用,而不是值本身。objobj2 指向同一个对象,所以修改 obj2.company 也会反映在 obj 上。

原始类型引用类型
存储位置栈内存堆内存
赋值方式拷贝值拷贝引用
类型nullundefinednumberstringbooleansymbolbigintobjectarrayfunction
修改独立性互不影响相互影响

三、null 与 undefined —— 看似相同,实则不同

let obj3 = {
    name: "1111",
    address: null
};
console.log(obj3.address); // null  —— 主动设置为空
console.log(obj3.age);     // undefined —— 属性根本不存在
nullundefined
含义空值(人为赋予)未定义(系统默认)
谁设置的开发者主动赋值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.10.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   → 系统默认"未定义"
│   ├── numberIEEE 754,小心精度!
│   ├── bigint      → 超大整数的救星
│   ├── string
│   ├── boolean
│   └── symbol
└── 引用类型 (Reference)
    └── object / array / function → 拷贝引用,修改共享

d16d3e4968adc3d46f1185db288eaa66.png 理解这些底层机制,能帮你避免写出一堆"玄学 bug"。记住三件事:

  1. 赋值时想清楚是值拷贝还是引用拷贝
  2. 浮点数运算永远不要直接 === 比较
  3. 大对象不用了记得断引用(= null