JavaScript 值的真实工作原理(原始类型与对象)

JavaScript 原始类型 vs 对象的所有误区、不可变性、按共享传递、拷贝、引用、V8 存储原理,是 JS 最核心的底层基础。
一、开篇直击经典问题
const original = { name: "Alice" }
const copy = original
copy.name = "Bob"
console.log(original.name) // "Bob"
为什么改拷贝会影响原对象?答案在于:原始类型按值独立,对象按引用共享。
破除流传最广的谣言 ❌
表格
| 常见谣言 | 真实情况 |
|---|---|
| “原始类型在栈上,对象在堆上” | 这是实现细节,不是标准,且并不完全正确 |
| “原始类型按值传递,对象按引用传递” | JS 统一使用 按共享传递(call by sharing) |
| “值类型 vs 引用类型” | ES 标准只定义:原始类型、对象 |
| “const 让对象不可变” | const 只禁止重新赋值,不禁止修改内容 |
二、标准定义:只有两类值
ECMAScript 标准只区分两种数据:
1. 原始类型 Primitive
- string
- number
- bigint
- boolean
- undefined
- null
- symbol
2. 对象 Object
- 普通对象
{} - 数组
[] - 函数
- Date / Map / Set 等
真正的区别只有一个:不可变性
- 原始类型:不可变(immutable)
- 对象:可变(mutable)
三、行为对比
原始类型:不可变、独立
- 无法修改本身,只能替换
- 拷贝后互不影响
- 按值比较
let a = "hello"
let b = a
b = "world"
console.log(a) // "hello" 不受影响
对象:可变、共享
- 可以修改内部属性
- 赋值时拷贝引用(指针)
- 多个变量指向同一个对象
- 按引用身份比较
let a = { name: "A" }
let b = a
b.name = "B"
console.log(a.name) // "B" 被影响
四、房子钥匙类比(最易懂)
-
原始类型 = 便签复制一张便签,两张完全独立,改一张不影响另一张。
-
对象 = 房子与钥匙复制钥匙,两把钥匙开同一栋房子。任何人改动房子,所有人都能看到变化。
五、JS 真正的传参方式:按共享传递 Call-by-Sharing
这是前端面试最高频底层题。
规则:
- 函数传参永远拷贝引用(钥匙)
- 修改属性( mutation)会影响原对象
- 重新赋值参数不会影响原变量
1)修改属性 → 影响外部 ✅
function rename(person) {
person.name = "Bob"
}
const user = { name: "Alice" }
rename(user)
console.log(user.name) // "Bob"
2)重新赋值参数 → 不影响外部 ❌
function replace(person) {
person = { name: "Charlie" }
}
const user = { name: "Alice" }
replace(user)
console.log(user.name) // "Alice" 不变
六、比较规则
原始类型:按值比较
"hello" === "hello" // true
10 === 10 // true
对象:按引用身份比较
{} === {} // false
[] === [] // false
{name:"A"} === {name:"A"} // false
只有完全同一个对象才相等。
七、修改 vs 重新赋值 Mutation vs Reassignment
修改(Mutation)
改变对象内部内容:
obj.name = "Bob"
arr.push(1)
arr[0] = 99
重新赋值(Reassignment)
让变量指向新值:
obj = {}
arr = []
const 的真相
- const 禁止重新赋值
- const 不禁止修改对象 / 数组
const arr = [1,2,3]
arr.push(4) // ✅ 允许
arr = [] // ❌ 报错
想真正不可变?用 Object.freeze()(浅冻结)。
八、浅拷贝 vs 深拷贝
浅拷贝 Shallow Copy
只拷贝第一层,嵌套对象仍共享。
const copy = { ...obj }
const copy = Object.assign({}, obj)
const arrCopy = [...arr]
深拷贝 Deep Copy
所有层级完全独立,用:
const deepCopy = structuredClone(original)
九、V8 引擎真实存储方式
栈 / 堆模型是简化模型,不完全正确。
V8 真实规则:
- 只有小整数(Smi)直接存
- 字符串、大数、BigInt、对象 → 都存在堆
- 变量保存的是指针 / 引用
十、高频 BUG 合集
- 拷贝数组却共享 → 用
[...arr]/slice() - 浅拷贝嵌套对象被改 → 用
structuredClone {} === {}为 false → 按引用比较- 函数内改参数影响外部 → 先拷贝再改
- 以为 const 让对象不可变 → 用
Object.freeze - sort 会改变原数组 → 先拷贝
[...nums].sort()
十一、核心结论(必须背)
- JS 只有原始类型和对象
- 原始类型不可变,对象可变
- 所有值传参都是 按共享传递
- 修改属性影响外部,重新赋值不影响
- 对象按引用比较,原始按值比较
- const 只限制赋值,不限制修改
- 浅拷贝共享嵌套,深拷贝完全独立
Symbols:原始类型中的唯一例外
Symbol 是原始类型,但拥有身份标识。即使描述文字一样,两个 Symbol 也永不相等。
const sym1 = Symbol("id")
const sym2 = Symbol("id")
sym1 === sym2 // false
修改 (Mutation) vs 重新赋值 (Reassignment)
修改(Mutation)
直接改变对象内部内容,不改变引用。
arr.push(4)
obj.name = "Bob"
delete obj.age
重新赋值(Reassignment)
让变量指向一个全新的值。
arr = [1,2,3]
obj = {}
a = 100
const 的巨大误区
const 只做一件事:禁止变量重新赋值
但它 完全不禁止对象 / 数组修改。
const user = { name: "Alice" }
user.name = "Bob" // ✅ 允许
user = {} // ❌ 报错
若要真正不可变,必须使用:Object.freeze ()(浅冻结)
const user = Object.freeze({ name: "Alice" })
user.name = "Bob" // 无效
注意:freeze 是浅冻结,嵌套对象依然能改。
浅拷贝 vs 深拷贝(完整版)
浅拷贝
只复制第一层,嵌套对象仍然共享。
方法:
{ ...obj }Object.assign({}, obj)[...arr]arr.slice()
问题:
const copy = { ...user }
copy.address.city = "XXX" // 原对象也会变
深拷贝
复制所有层级,完全独立。
现代标准方法:
const deepCopy = structuredClone(original)
支持:
- 对象 / 数组
- Date / Map / Set
- 循环引用
- 不支持:函数、Symbol
旧式替代:
JSON.parse(JSON.stringify(obj))
缺点:丢失 Date、undefined、函数、循环引用。
V8 引擎如何真正存储值(破除栈 / 堆谣言)
谣言:
“原始类型存在栈里,对象存在堆里。”
事实(V8 真实机制):
-
只有小整数 Smi 直接存储-2³¹ ~ 2³¹-1
-
其他所有东西都在堆里
- 字符串
- 长数字
- BigInt
- 对象、数组、函数
- 布尔、null、undefined 内部也是堆对象
-
变量保存的是 指针(引用)
所以:字符串绝对不在栈上!它是动态长度,一定存在堆。
JavaScript 最常见的 6 大坑
1. 数组赋值后互相影响
const a = [1,2,3]
const b = a
b.push(4)
// a 也变了
✅ 解决:拷贝
const b = [...a]
2. 函数内修改参数影响外部
function f(arr) {
arr.push(99)
}
✅ 解决:先复制
const newArr = [...arr]
3. 浅拷贝导致嵌套对象被改
const copy = { ...user }
copy.address.city = "LA" // 原对象也变
✅ 解决:深拷贝
structuredClone(user)
4. 相信 {} === []
{} === {} // false
[] === [] // false
✅ 解决:比较内容
5. 以为 const 让对象不可变
const user = {}
user.name = "xx" // 依然可以改
✅ 解决:Object.freeze ()
6. sort 会修改原数组
const sorted = arr.sort()
✅ 解决:
const sorted = [...arr].sort()
最佳实践(必读)
- 尽量把对象当不可变使用
- 优先 const,减少意外赋值
- 修改数组前先拷贝
- 嵌套对象用 structuredClone
- 函数参数不要直接修改
- 记住:对象按引用比较
最终核心总结(面试必背)
- JavaScript 只有原始类型和对象
- 本质区别:原始不可变,对象可变
- JS 传参一律是 按共享传递
- 修改属性会影响原对象;重新赋值不会
- 对象比较的是引用,不是内容
- const 不保证不可变,只禁止赋值
- 浅拷贝共享嵌套,深拷贝完全独立
- 栈 / 堆是简化模型,V8 只有小整数直接存储
🧩 新概念 + 类似概念 汇总
1. 核心基础概念
- Primitive(原始类型)同类:值类型、基础类型、原子类型
- Object(对象)同类:引用类型、复合类型、结构类型
- Immutability(不可变性)同类:只读、不可修改、只能替换
- Mutability(可变性)同类:可修改、可更新、原地变更
2. 赋值与传递
- Call-by-Sharing(按共享传递)同类:按对象传递、拷贝引用、共享指针
- Call-by-Value(按值传递)同类:值拷贝、独立副本
- Call-by-Reference(按引用传递)同类:别名、指针传递(JS 不使用)
- Reference(引用)同类:指针、地址、指向堆内存
3. 拷贝与克隆
- Shallow Copy(浅拷贝)同类:一层拷贝、嵌套共享
- Deep Copy(深拷贝)同类:完全克隆、全层级独立
- structuredClone同类:原生深拷贝、支持循环引用
4. 变量与赋值
- Reassignment(重新赋值)同类:修改变量指向
- Mutation(修改 / 突变)同类:原地修改内容、属性变更
- const 限定同类:禁止重新赋值、不保证不可变
5. 比较规则
- Value Equality(值相等)同类:内容相同即相等
- Reference Equality(引用相等)同类:同一个对象才相等、身份相等
6. 引擎底层
- Heap(堆)同类:动态内存、复杂数据存储
- Stack(栈)同类:简易存储模型(非真实机制)
- Smi(Small Integer)同类:V8 小整数、直接存储
- Pointer Tagging(指针标记)同类:V8 内部值存储优化
7. 可变性与不可变性
- Immutability(不可变性)同类:只读、不可修改、纯值、无副作用
- Mutation(修改 / 突变)同类:原地修改、更新内容
- Reassignment(重新赋值)同类:修改变量指向、覆盖绑定
8. 函数传参机制
- Call-by-Sharing(按共享传递)同类:按对象传递、拷贝引用
- Call-by-Value(按值传递)同类:值副本、独立拷贝
- Call-by-Reference(按引用传递)同类:别名、指针(JS 不使用)
9. 引用与身份
- Reference(引用)同类:指针、内存地址、对象索引
- Identity(对象身份)同类:引用相等、同一对象
- Reference Equality(引用相等)同类:=== 比较对象
10. 不可变 API
- Object.freeze()同类:浅冻结、只读对象、防篡改
- Deep Freeze(深冻结)同类:递归冻结、完全不可变
11. 常见误区概念
- Global Pollution(全局污染)同类:全局变量冲突
- Side Effect(副作用)同类:意外修改外部状态
- Circular Reference(循环引用)同类:互相引用、结构体循环

更多推荐

所有评论(0)