什么是原始类型和引用类型?它们之间有什么区别?
核心答案
JavaScript 中的数据类型分为两大类:
原始类型(Primitive Types):string、number、boolean、null、undefined、symbol、bigint
- 存储在栈内存中
- 按值访问,赋值时复制的是值本身
- 不可变(immutable)
引用类型(Reference Types):Object、Array、Function、Date、RegExp 等
- 存储在堆内存中,栈中保存的是指向堆的引用地址
- 按引用访问,赋值时复制的是引用地址
- 可变(mutable)
深入解析
1. 内存存储机制
栈内存 (Stack) 堆内存 (Heap)
┌─────────────────┐ ┌─────────────────┐
│ a: 10 │ │ │
│ b: 'hello' │ │ { name: 'Tom' }│ ← 0x001
│ obj: 0x001 ─────│───────│ │
└─────────────────┘ └─────────────────┘
- 栈内存:空间小、访问速度快、系统自动分配释放
- 堆内存:空间大、访问速度相对慢、需要手动或 GC 回收
2. 赋值行为差异
原始类型 - 值复制:
let a = 10;
let b = a; // b 得到 a 的副本
b = 20;
console.log(a); // 10 (a 不受影响)
引用类型 - 引用复制:
let obj1 = { name: 'Tom' };
let obj2 = obj1; // obj2 得到同一个引用地址
obj2.name = 'Jerry';
console.log(obj1.name); // 'Jerry' (obj1 也被修改了)
3. 不可变性的含义
原始类型的"不可变"指的是值本身不可变,而不是变量不可重新赋值:
let str = 'hello';
str[0] = 'H'; // 无效操作
console.log(str); // 'hello' (字符串本身不可变)
str = 'Hello'; // 这是重新赋值,创建了新的字符串
4. 常见误区
误区一:typeof null === 'object'
typeof null // 'object' 这是 JS 的历史 bug
误区二:认为 const 声明的引用类型不可修改
const arr = [1, 2, 3];
arr.push(4); // 可以!const 只保证引用地址不变
arr = [1, 2, 3, 4]; // 报错!不能重新赋值
代码示例
比较方式的差异
// 原始类型 - 按值比较
let a = 'hello';
let b = 'hello';
console.log(a === b); // true
// 引用类型 - 按引用比较
let obj1 = { x: 1 };
let obj2 = { x: 1 };
console.log(obj1 === obj2); // false (不同的引用地址)
let obj3 = obj1;
console.log(obj1 === obj3); // true (同一个引用地址)
函数传参的影响
// 原始类型传参
function changeValue(x) {
x = 100;
}
let num = 10;
changeValue(num);
console.log(num); // 10 (不变)
// 引用类型传参
function changeObj(obj) {
obj.name = 'changed';
}
let person = { name: 'original' };
changeObj(person);
console.log(person.name); // 'changed' (被修改了)
深拷贝 vs 浅拷贝
// 浅拷贝 - 只复制第一层
let original = { a: 1, b: { c: 2 } };
let shallow = { ...original };
shallow.b.c = 999;
console.log(original.b.c); // 999 (嵌套对象被影响)
// 深拷贝 - 完全独立的副本
let deep = JSON.parse(JSON.stringify(original));
// 或使用 structuredClone(original)
面试技巧
面试官可能的追问方向
-
如何判断数据类型?
typeof的局限性(null、数组)instanceof的原理Object.prototype.toString.call()最准确
-
如何实现深拷贝?
- 递归实现
- 循环引用的处理
structuredCloneAPI
-
Symbol 的应用场景?
- 对象属性键的唯一性
- 内置 Symbol(如
Symbol.iterator)
-
BigInt 和 Number 的区别?
展示深度的方式
- 提到 V8 引擎中的 SMI(小整数优化)
- 解释为什么
typeof null === 'object'是历史遗留问题 - 对比其他语言(如 Java)的值类型/引用类型
一句话总结
原始类型存值在栈、按值访问、不可变;引用类型存值在堆、按引用访问、可变。赋值和传参时,原始类型复制值,引用类型复制地址。