一、变量提升的本质:JS 的「预编译魔术」
console.log(a); // undefined(而不是报错!)
var a = 10;
-
var 的规则:
声明会被隐形搬运到当前作用域顶部,但赋值保持原位,相当于:var a; // 声明被提升 console.log(a); // 此时 a=undefined a = 10; // 赋值留在原地 -
let/const 的差异:
虽然也有提升,但会进入 TDZ(暂时性死区),在声明前访问直接报错:console.log(b); // ❌ ReferenceError let b = 20;
二、作用域战场:var vs let/const
| var | let/const | |
|---|---|---|
| 作用域类型 | 函数级(整个函数内有效) | 块级({} 内有效) |
| 重复声明 | ✅ 允许 | ❌ 禁止 |
| 全局污染 | 自动成为 window 属性 | 与 window 对象隔离 |
经典陷阱对比:
// var 的循环陷阱
for(var i=0; i<3; i++){
setTimeout(()=>console.log(i), 0) // 输出 3,3,3
}
// let 的正确表现
for(let i=0; i<3; i++){
setTimeout(()=>console.log(i), 0) // 输出 0,1,2
}
三、const 的深层原理:锁柜门还是锁物品?
const ID = 100;
ID = 200; // ❌ 报错(基本类型不可变)
const user = {name: 'Alice'};
user.name = 'Bob'; // ✅ 允许(对象属性可变)
user = {}; // ❌ 报错(地址不可变)
-
底层逻辑:
- 简单类型(Number/String等):直接存储在栈内存,
const冻结值 - 复杂类型(Object/Array等):数据在堆内存,
const只冻结栈中存储的内存地址(类似锁住储物柜门,不锁柜内物品)
- 简单类型(Number/String等):直接存储在栈内存,
四、堆栈视角:值传递 vs 地址传递
| 简单数据类型(值传递) | 复杂数据类型(地址传递) | |
|---|---|---|
| 存储位置 | 栈内存(直接存值) | 堆内存存数据,栈内存存地址指针 |
| 赋值行为 | 复制整个值(类似复印文件) | 复制地址指针(类似给储物柜钥匙) |
| 典型案例 | let a=1; b=a;(b 独立) | let arr1=[]; arr2=arr1;(共享数据) |
代码示例:
// 值传递(独立副本)
let x = 10;
let y = x;
y = 20;
console.log(x); // 10(不受影响)
// 地址传递(共享数据)
let obj1 = {count: 10};
let obj2 = obj1;
obj2.count = 20;
console.log(obj1.count); // 20(同步修改)
五、三大声明终极对比表
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数级 | 块级 | 块级 |
| 提升表现 | 声明提升,值=undefined | 声明提升,但触发 TDZ | 同 let |
| 重复声明 | ✅ | ❌ | ❌ |
| 值修改权 | ✅ | ✅ | 简单类型❌ / 对象属性✅ |
| 内存管理 | 可能内存泄漏 | 块结束自动释放 | 同 let |
🔥 核心总结
- 变量提升是 JS 引擎的「预扫描」,
var声明会被提到作用域顶部但未赋值,let/const会进入 TDZ 禁区 - 简单数据类型在栈内存独立存储,赋值时复制值;复杂数据类型在堆内存共享存储,赋值时复制地址
- 默认使用
const,需要修改变量时用let,除非维护老代码否则不用var
一句话记忆:
var是随意张贴的小广告(随处存在,可覆盖)let是精准坐标的公告板(块级可控,可修改)const是带玻璃罩的展示柜(地址锁定,内容可调)
(用 console 多实践这些特性,理解会更深刻哦!💻)