——深入探索JavaScript底层的内存栈与堆
前言:从“快递柜”到“图书馆” 📦📚
这篇文章的主要内容是一个“看似简单却暗藏玄机”的话题:const的赋值机制。
你可能知道,const声明的是常量,但它的“常量”可不是铁板一块!它既能像快递柜(内存栈)一样“固执己见”,又能像图书馆(内存堆)一样“灵活多变”。
一、const的“铁律”:值不可变? 🔐
在ES5时代,var是JavaScript唯一的变量声明方式,但它有个致命缺点:没有块级作用域,还容易污染全局变量(比如window.xxx)。
ES6的const和let横空出世,直接解决了这些问题,并带来了“块级作用域”的新世界!
简单数据类型:值不可变
const age = 18;
age = 20; // ❌ 报错!你无法修改一个“铁板钉钉”的值。
- 原理:简单数据类型(数字、字符串、布尔值等)直接存储在内存栈中,
const会“死死守住”这个值,不允许你改天换地。 - 比喻:就像快递柜里的包裹,地址是固定的,内容也是固定的,想偷偷调包?门都没有!📦
二、const的“双面间谍”:值传递 vs 引用传递 🕵️♀️
复杂数据类型:地址不可变,值可以变
const user = { name: "Alice" };
user.name = "Bob"; // ✅ 允许!
user = { name: "Charlie" }; // ❌ 报错!
- 原理:对象、数组等复杂数据类型存储在内存堆中,
const只“守住”栈中的引用地址,但堆中的值可以随意修改。 - 比喻:你手里有一张图书馆的借书卡(地址),你可以借不同的书(修改值),但卡本身不能换成别人的卡(地址不变)。📚
三、内存栈与堆的“冰与火之歌” ⚔️🔥
内存栈:快递柜的“快与稳”
- 特点:连续空间、访问速度快、自动释放。
- 适用:简单数据类型和函数调用上下文。
- 示例:
const a = 10; // 值直接存在栈里,修改会报错。 const b = "Hello"; // 字符串也存在栈里(字面量时)。
内存堆:图书馆的“大而慢”
- 特点:非连续空间、动态分配、手动管理(靠GC回收)。
- 适用:对象、数组等复杂数据类型。
- 示例:
const obj = { key: "value" }; // 栈中存地址,堆中存实际数据。 obj.key = "new value"; // 堆中数据可以改,但地址不变。
四、赋值操作的“左右互搏” 🤹♂️
简单数据类型:值传递
let x = 10;
let y = x;
x = 20;
console.log(x, y); // 20, 10(y不受影响)
- 原理:简单数据类型是值传递,赋值时会复制一份值到新变量的栈中。
复杂数据类型:引用传递
const arr1 = [1, 2, 3];
const arr2 = arr1;
arr1.push(4);
console.log(arr1, arr2); // [1,2,3,4], [1,2,3,4](两者指向同一块内存)
- 原理:复杂数据类型是引用传递,赋值时只复制栈中的地址,堆中的数据共享。
五、const的“倔强”与“妥协” 🤷♂️
- 倔强:绝不允许修改栈中的值(如
const a = 10)。 - 妥协:允许修改堆中的值(如
const obj = { a: 1 }; obj.a = 2;)。
为什么?
因为const的本质是“守住栈中的地址”,而不是“守住堆中的内容”。堆中的数据是动态的,JavaScript的垃圾回收机制(GC)会自动处理它们的生命周期。
六、const在函数参数中的“防坑指南” 🛡️
——从“铁面无私”到“灵活变通”的进阶之旅
1. 基本类型参数:铁面无私,绝不妥协
function modifyValue(x) {
const fixedX = x; // 用const“冻结”参数副本
x = 10; // 允许!但不会影响fixedX
console.log(fixedX); // 5
}
const num = 5;
modifyValue(num);
console.log(num); // 5(原始值未被修改)
- 原理:
const不能直接修饰函数参数(语法错误),但可以通过赋值给const变量实现“局部保护”。 - 比喻:快递柜加锁(栈中值),别人无法偷偷调包你的包裹!📦
2. 复杂类型参数:灵活变通,允许“小动作”
function modifyObject(obj) {
obj.key = "new value"; // ✅ 允许!
obj = { key: "another" }; // ❌ 报错!不能修改obj的引用
}
const myObj = { key: "original" };
modifyObject(myObj);
console.log(myObj); // { key: "new value" }(属性被修改)
- 原理:
- 栈中的引用地址:
const“守住”堆中对象的引用地址(即obj指向的内存地址不能变)。 - 堆中的数据:允许修改对象内部的属性值。
- 栈中的引用地址:
- 比喻:你手里的借书卡(地址)不能换,但借的书(数据)可以更新~📚
3. 为什么const不能直接修饰参数?
- 历史原因:JavaScript的函数参数设计初衷是按值传递(基本类型)和按引用传递(复杂类型)。
- 设计哲学:
const的“守恒”特性与函数参数的灵活性冲突,因此ES6+禁止直接用const修饰参数。
七、const的“婚姻法”💍
const就像民政局发的结婚证:
- 简单类型:你俩感情稳定,民政局不允许你们离婚(改值)。
- 复杂类型:你俩感情复杂,民政局允许你们闹离婚(改属性),但禁止你们跑路(改引用)。