大家好,今天我们来聊聊 JavaScript 的数据类型。如果你是刚接触 JS 的新手,可能会被 null、undefined、Symbol 这些东西搞得晕头转向。别担心,这篇文章会用最通俗的方式,带你一步步搞懂它们。
一、JS 到底有多少种数据类型?
根据 ECMA262 规范(你可以理解为 JS 的"宪法"),JavaScript 一共有 8 种数据类型。
我们可以把它们分成两大阵营:
javascript
原始类型(Primitive Types)--- 6 种(其中 Number 和 BigInt 同属 numeric 数值类)
├── Number 数值
├── String 字符串
├── Boolean 布尔值
├── Null 空
├── Undefined 未定义
├── Symbol 唯一标识符(ES6 新增)
└── BigInt 大整数(ES6 新增)
引用类型(Reference Types)--- 1 种
└── Object 对象
注意:ES6 之前 JS 只有 6 种类型。Symbol 和 BigInt 是后来加入的"新成员"。
二、原始类型:JS 的基本"积木"
原始类型就像乐高积木里最基础的小方块------简单、独立、不可再分。
2.1 Number --- 数字
ini
let age = 18;
let price = 99.9;
JS 里所有数字都是 Number 类型,不分整数和浮点数。
但有个大坑 :JS 在存小数时不够精确 。为什么呢?因为计算机底层用的是二进制,像 0.1 + 0.2 这种运算,结果并不是你期望的 0.3:
arduino
console.log(0.1 + 0.2); // 0.30000000000000004
就像我们无法用十进制精确表示 1/3(0.33333...)一样,计算机也无法用二进制精确表示某些小数。这是所有语言都有的问题,不是 JS 的锅。
2.2 String --- 字符串
ini
let name = "小明";
let greeting = '你好';
用单引号或双引号包起来的就是字符串,没什么特别的。
2.3 Boolean --- 布尔值
ini
let isLogin = true;
let isVip = false;
只有 true 和 false 两个值。常用于条件判断。
2.4 undefined --- "该来的没来"
undefined 只有一个值,就是 undefined。它表示某个东西存在,但还没有被赋值。它是一种"被动"的空。
什么时候你会遇到 undefined?来看这些常见场景:
javascript
// 场景1:声明了变量但没赋值
let a;
console.log(a); // undefined
// 场景2:访问对象不存在的属性
let obj = {};
console.log(obj.name); // undefined
// 场景3:函数没有返回值
function noReturn() {
// 啥也没 return
}
console.log(noReturn()); // undefined
// 场景4:访问数组越界的索引
let arr = [1, 2, 3];
console.log(arr[999]); // undefined
记住一句话:undefined = 该来的没来。你期待这里有个值,但它偏偏没有。
2.5 null --- "故意让它空的"
null 也只有一个值,就是 null。它和 undefined 很像,但含义完全不同:
-
null是主动设置的空------你故意告诉 JS:「这个变量我就是想让它空着」。它表示一个"有意设置为空"的对象引用。 -
undefined是被动产生的空------系统告诉你:「这个东西还没被初始化」let user = null; // "我现在不知道用户是谁,先空着,以后有了再填"
// 实用技巧:把不用的对象设为 null,帮助浏览器回收内存 let largeData = { data: new Array(10000000).fill("hello") }; largeData = null; // 告诉垃圾回收器:这块内存我不要了,你收走吧
小知识:
typeof null === "object"这是一个历史遗留 bug,别被它误导了------null 确实是原始类型,不是对象。
三、ES6 新增的两个"特种兵"
3.1 Symbol --- 独一无二的标识符
Symbol 是 ES6 引入的新类型,它的核心特点就两个字:唯一。
javascript
// 即使描述(label)一样,它们也绝对不相等
console.log(Symbol("id") === Symbol("id")); // false!
console.log(typeof Symbol("id")); // "symbol"
每个 Symbol() 创建出来都是世上独一无二的存在。你可以在括号里传一个标签用来调试,但这个标签不影响唯一性。
这在需要「绝不重复的 key」时特别好用:
javascript
let obj = {
[Symbol()]: '这个属性不会被意外覆盖',
prop: "普通属性"
};
console.log(obj.id); // undefined------Symbol 属性不可通过 .id 访问
Symbol 最常见的用途是给对象添加不会冲突的属性名。尤其在写库或框架时,你用 Symbol 做 key,就不用担心跟用户的属性名"撞车"了。
3.2 BigInt --- 超大整数随便算
JS 的 Number 类型有个安全范围:-(2⁵³ - 1) 到 2⁵³ - 1。超出这个范围,计算就会精度丢失。
来看个例子------两个超大数用普通 Number 相加,JS 直接懵了,算出来一个不可信的结果。这时候 BigInt 就派上用场了。只需在数字后面加个 n:
ini
let num1 = 999999999999999999999999999999999999999999999999999999999999999n;
let num2 = 123456789098765433467324577654789008733233456899003466788924243n;
console.log(num1 + num2); // 精确! ✅
console.log(typeof num1); // "bigint"
注意事项:
sql
// console.log(num1 + 1); // ❌ 报错!BigInt 不能和普通 Number 混算
console.log(num1 + 1n); // ✅ 必须两边都是 BigInt
四、原始类型 vs 引用类型:拷贝的两种姿势
这是很多初学者最困惑的地方。理解了它,你就真正入门了 JS。
4.1 原始类型:复印机式赋值
ini
let a = null;
let b = a; // 把 a 的值"复印"一份给 b
b = 2;
console.log(a); // null------a 完全不受影响
原始类型的赋值就像复印机------复印一份给你,原件和复印件各管各的,互不影响。
4.2 引用类型:共享地址式赋值
ini
let obj = { name: "张三" };
let obj2 = obj; // obj2 和 obj 指向同一个对象
obj2.company = "字节跳动";
console.log(obj); // { name: "张三", company: "字节跳动" }
console.log(obj2); // { name: "张三", company: "字节跳动" }
// 两个输出一模一样!
对象是引用类型,赋值时传的是地址(你可以理解成"门牌号"),而不是实际数据。obj 和 obj2 手里拿的是同一把钥匙,指向同一间房子------不管谁进去装修,另一个人看到的也是装修后的样子。
五、从内存角度看,一切就更清晰了
如果你还想再深入一点,我们来聊聊内存。别怕,用个比喻帮你说清楚。
5.1 计算机的"工作台"
回到计算机的祖师爷------冯·诺依曼体系。市面上几乎所有计算设备(你的手机、笔记本、台式机)都遵循这个架构:
运算器 + 控制器 + 存储器 + 输入设备 + 输出设备
你的代码躺在硬盘(外存)里 → 运行的时候加载到内存 → CPU 去内存里取指令、算数据。
5.2 栈内存 vs 堆内存
JS 引擎在执行代码时,会创建执行上下文 (你可以理解为"当前代码的运行环境快照"),包含变量环境、词法环境,然后把它推入调用栈。
- 栈内存 :快,但是空间小。存的是原始数据类型的值本身 ,以及引用数据类型的地址(门牌号)。函数执行上下文占多大空间在编译阶段就能算出来,分配得刚刚好。函数执行完、出栈,指针一偏就切到下一个上下文了------快速、稳定。
- 堆内存 :大,但是慢。存的是对象本身的数据。为什么对象不放栈里?因为对象可能很大、可能动态增删属性,大小没法在编译时确定,只能放到"随便长"的堆里。
这就完美解释了:
- 原始类型赋值 = 复印:值直接存在栈里,拷贝就是复制一份值
- 引用类型赋值 = 共享地址:栈里存的只是堆内存的门牌号,拷贝的是门牌号,不是房子
六、一张图总结
类型
分类
赋值方式
一句话
Number
原始
拷贝值
存数字,小数偶尔会不精确
String
原始
拷贝值
存文本
Boolean
原始
拷贝值
只有 true / false
null
原始
拷贝值
主动清空,表示"故意没有"
undefined
原始
拷贝值
被动缺失,表示"该来的没来"
Symbol
原始
拷贝值
独一无二的标识符
BigInt
原始
拷贝值
超大整数精确计算
Object
引用
拷贝地址
所有对象的祖宗,传地址不传值
写在最后
JavaScript 的数据类型是地基。地基不牢,后面学原型链、闭包、异步这些东西就容易塌。强烈建议你打开浏览器控制台,把文中的例子一个个敲一遍,亲手感受 null 和 undefined 的区别、Symbol() 的唯一性、BigInt 的精度------动手永远是最好的学习方式。
源码文件就在这个 type 目录下,每个 .js 文件对应一个独立的知识点,注释写得清晰明了,配合这篇文章一起看,效果更好 🚀