做前端这些年,面试过不少人,也带过不少新人,我发现一个很有意思的现象:很多人写了两三年业务代码,却依然说不清楚 JS 里的“基本数据类型”和“引用数据类型”究竟差在哪里。这个知识点表面上看是八股文里的第一页,但实际上它决定了你写代码会不会遇到“改了一个值,另一处也凭空变了”的诡异 bug,也决定了你能不能真正看懂 Vue 响应式实现、会不会在深拷贝上翻车。可以说,它既是前端入门必须跨过的那道门槛,也是工作多年后还能拿出来排查线上事故的硬本事。
这篇文章不打算讲教科书那套死定义,我会从“它到底是什么、内存里怎么放、判断和转换时有哪些坑、复制时为何出事”这几个真实场景切入,把基本类型和引用类型一次讲透。文章里会出现大量可直接运行的代码示例、面试常见陷阱和实际项目里踩坑后的修复方案,不管是刚入门的新人,还是想系统梳理一遍的资深开发,读完后都能直接应用到自己的代码里。
1. 先认清楚:JS 里的数据类型到底分哪几个阵营
1.1 七个基本数据类型逐个过一遍
ES2020 之后,JavaScript 的基本数据类型一共有七种:string、number、boolean、null、undefined、symbol、bigint。它们统称为原始值(primitive value),核心特点就是“单值”且“不可再拆解”。
string:字符串类型,单双引号都能用,模板字符串是 ES6 之后的主流写法。要注意它是不可变的,任何字符串方法(比如split、concat)返回的都是一个新字符串,原始值不会被改动。number:数字类型,这里的门道最多。它遵循 IEEE 754 双精度标准,所以0.1 + 0.2不等于0.3这种经典问题就出在它身上。它还包含NaN、Infinity、-Infinity这些特殊值,其中NaN是唯一一个不等于自身的值。boolean:布尔类型,只有true和false两个值。写条件判断时,0、''、null、undefined、NaN都会被转换为false,这几个值统称 falsy 值。null:表示“有意的空值”。类型判断时typeof null返回'object',这是从 ES 第一版就留下的历史 bug,但因为兼容性问题永远修不了,很多人第一次在这里被绕晕。undefined:表示“声明了但没有赋值”。undefined和null在==比较时相等,在===比较时不相等,这是面试高频考点。symbol:ES6 引入的第七种类型,用于生成唯一标识。每次调用Symbol()返回的都不相同,适合做对象属性名,能在一定程度上避免属性名冲突。bigint:ES2020 加入,用于表示超出Number.MAX_SAFE_INTEGER(即2^53 - 1)的大整数。它和number是两种完全不同的类型,直接混用会报错。
这七种类型本质上都“自带值”,它们不依赖任何外部内存结构,赋值给变量时,变量存的就是那个值本身。这一点很关键,后面讲复制行为时你会反复体会到它和引用类型的天壤之别。
1.2 引用类型的大本营:Object 家族
引用类型在 JS 里实际上只有一种,就是 Object,但 Object 是一个抽象的大类,日常开发中它能具体化为无数种形态:数组 Array、函数 Function、日期 Date、正则 RegExp、还有 ES6 之后的 Map、Set、WeakMap、WeakSet,包括通过 class 声明的实例,底层都属于对象。
数组很好理解,它就是带索引的有序列表,但它也是对象,所以 typeof [] 返回 'object'。函数更特殊,它本身是可执行的对象,typeof function(){} 返回 'function',这算 JS 给函数开的一个“特权地位”,但底层仍然是对象,能挂属性、能被存储、能作为参数传递。
引用类型和基本类型最本质的差异在于:变量里实际保存的不是对象本身,而是指向“堆内存”中那个真实对象的一个“指针”或者说“引用”。所以当你执行 const a = { name: '张三' }; const b = a; 的时候,a 和 b 指向的是同一个内存对象。修改 b.name,a.name 也会跟着变。这是引用类型最核心的行为特征,几乎所有“变量被莫名修改”的 bug 都源自于此。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 存储与复制行为的底层差异:栈、堆和“门牌号”
2.1 用“门牌号”和“房间”理解栈与堆
严格来说,JavaScript 引擎并没有像 C/C++ 那样让开发者直接控制栈与堆的分配,但为了讲清楚基本类型和引用类型的差异,业界一直沿用“栈 + 堆”这个概念模型去理解。
可以打个比方:把内存想象成一座公寓楼。基本类型的值很小,直接把值写在“门牌号”位置,比如变量 a 的门牌号上就写着 42,你去找它时一眼就能看到数字本身。引用类型则是在门牌号上只写一个“房间号”(内存地址),真实的房间在公寓后面那一排大楼里(堆内存),你要拿到真正的数据,必须先按房间号进去取。
这个模型能解释很多现象。因为基本类型值直接存在栈帧里,所以复制变量时是把值完完整整地复制一份,两个变量互不影响;而引用类型在栈里存的只是地址,复制变量时复制的只是“房间号”,两个变量最终仍指向同一间房。明白了这一点,你在思考修改对象为什么会影响另一个变量时,就不需要死记硬背了。
2.2 基本类型的复制是“复印文件”,引用类型的复制只是“借了把钥匙”
基本类型复制非常好理解:
javascript复制let a = 42;
let b = a;
b = 100;
console.log(a); // 42
console.log(b); // 100
这里 a 的值 42 被完整复印给了 b,之后 b 改成 100 跟 a 没有任何关系。这种赋值方式叫“值传递”。
再看引用类型:
javascript复制con
