JavaScript的数据类型是每个前端开发者绕不开的基础知识,但你真的理解透彻了吗?nullundefined到底有什么区别?SymbolBigInt又是做什么的?引用类型在内存中是如何存放的?今天我们就来彻底搞懂这些问题。

一、JavaScript到底有多少种数据类型?

根据ECMA262规范,JavaScript目前共有8种数据类型,可以划分为两大类:

原始数据类型(Primitive Types)- 7种

类型说明示例
Number数值(整数和浮点数)423.14
String字符串"hello"
Boolean布尔值truefalse
null空值null
undefined未定义undefined
Symbol唯一标识符(ES6新增)Symbol('id')
BigInt大整数(ES6新增)9007199254740991n

引用数据类型 - 1种

  • Object:包括普通对象、数组、函数、日期、正则等所有复杂结构

💡 在ES6之前,JavaScript只有6种数据类型(Number、String、Boolean、null、undefined、Object)。ES6引入了SymbolBigInt,扩充到8种。

二、深入理解原始数据类型

1. Number类型——那些“不精确”的坑

JavaScript中的Number采用IEEE 754双精度浮点数标准存储,这导致了一个经典问题:

let a = 0.1;
let b = 0.2;
console.log(a + b); // 0.30000000000000004

这不是bug,而是二进制表示小数的天然缺陷。就像十进制无法精确表示1/3一样,二进制也无法精确表示0.1。

解决方案:

  • 使用toFixed()控制小数位数

  • 转为整数计算后再还原

  • 使用BigInt处理大整数场景

2. null与undefined——最容易被混淆的一对

这两个类型经常让初学者困惑,它们的本质区别在于:

对比维度undefinednull
语义未初始化/不存在空值(有意设置为空)
出现场景变量声明未赋值、对象属性不存在、数组索引越界、函数无返回值手动清空变量、表示“空对象引用”
类型检测typeof undefined → "undefined"typeof null → "object"(历史遗留bug)

代码示例:

// undefined 出现的场景
let a;                    // 声明未赋值
console.log(a);           // undefined

let obj = {};
console.log(obj.property); // undefined

function noReturn() {}
console.log(noReturn());   // undefined

let arr = [1,2,3];
console.log(arr[5]);       // undefined

// null 的使用场景
let obj1 = { name: "xll" };
let obj2 = obj1;          // 引用赋值
obj2.company = "快手";
console.log(obj1);        // { name: "xll", company: "快手" }

// 手动释放内存
let largeObject = {
    data: new Array(100000000).fill("hgh")
};
largeObject = null;       // 解除引用,等待垃圾回收

最佳实践:

  • 需要“清空”一个变量时,使用null

  • 需要判断变量是否被赋值时,使用undefined

3. Symbol——创造独一无二的标识符

Symbol是ES6新增的原始类型,它的核心特性是绝对唯一

console.log(Symbol('zzh') === Symbol('zzh')); // false
console.log(typeof Symbol('zzh'));            // "symbol"

// 作为对象属性键,避免命名冲突
let obj = {
    [Symbol()]: 'value',
    prop: "2"
};

4. BigInt——突破Number的精度限制

Number的安全整数范围是 -2^53+1 到 2^53-1BigInt通过后缀n或构造函数创建,可以表示任意大整数:

let num1 = 999999999999999999999999999999999999999999999999999999999999999n;
let num2 = 123456789098765433467324577654789008733233456899003466788924243n;
console.log(num1 + num2);  // 正常计算
console.log(typeof num1);  // "bigint"

// 注意:BigInt不能与Number混用
// console.log(num1 + 1);   // 报错!
console.log(num1 + 1n);     // 正确

三、引用数据类型——Object的无限可能

Object是JS中最灵活的类型,包括:

// 普通对象
const person = { name: "Alice", age: 25 };

// 数组
const colors = ["red", "green", "blue"];

// 函数
function sayHello() { console.log("hello"); }

// 日期
const now = new Date();

// 正则
const pattern = /abc/;

四、从内存分配理解数据类型——栈与堆

这是理解JS数据类型的关键!我们需要先了解计算机的内存模型。

冯·诺依曼体系结构

计算机由运算器、控制器、存储器、输入设备、输出设备组成。我们的代码存储在硬盘(外存)中,程序运行时被加载到内存(存储器)中执行。

JavaScript的内存分配

JavaScript在执行时,内存分为栈(Stack)堆(Heap)两个区域:

特性栈内存堆内存
存储内容原始值、引用地址实际对象数据
访问速度相对慢
空间大小小(固定大小)大(动态分配)
管理方式自动、LIFO(后进先出)自动垃圾回收
作用域执行上下文、函数调用全局、长期存活的对象

赋值方式的根本区别

原始类型——拷贝式赋值:

let a = null;
let b = a;   // 拷贝了一份值
b = 2;
console.log(a, b);  // null, 2 → 互不影响

引用类型——引用式赋值:

let obj1 = { name: "xll" };
let obj2 = obj1;    // 拷贝的是地址,指向同一个对象
obj2.company = "快手";
console.log(obj1);  // { name: "xll", company: "快手" }

执行上下文与调用栈

当JavaScript代码执行时:

  1. 代码从硬盘加载到内存

  2. 创建全局执行上下文(包含变量环境、词法环境)

  3. 执行上下文被推入调用栈

栈内存中的执行上下文在编译阶段就能确定需要多少空间,函数执行完毕后立即出栈,通过指针偏移就能快速切换上下文——这就是栈高效的原因。

而对象的具体数据存储在堆内存中,栈中只保存一个引用地址(相当于快捷方式)。

五、总结对比表

数据类型分类存储位置赋值方式可变性
Number原始拷贝值不可变
String原始拷贝值不可变
Boolean原始拷贝值不可变
null原始拷贝值不可变
undefined原始拷贝值不可变
Symbol原始拷贝值不可变
BigInt原始拷贝值不可变
Object引用栈(地址)+ 堆(数据)拷贝引用可变

六、写在最后

理解JavaScript数据类型的关键在于两点:

  1. 语义层面null是“空值”,undefined是“未定义”,Symbol是“唯一”,BigInt是“大整数”

  2. 内存层面:原始值存栈中,引用值存堆中,赋值时一个是拷贝值,一个是拷贝引用

掌握这些,你就能轻松应对日常开发中的类型相关bug,也能更好地理解闭包、深拷贝、垃圾回收等进阶概念。

希望这篇文章对你有帮助!欢迎在评论区讨论交流。

 

更多推荐