🌟 前言:你不知道的 JavaScript 小知识,你搞明白了吗?
"JavaScript 是一门灵活的语言,但也正因为它的'宽容',埋下了许多陷阱。"
------《你不知道的 JavaScript》
你是否遇到过:
- ❓ 为什么变量还没定义就能访问,值却是
undefined? - ❓ 为什么
const定义的对象,属性还能改? - ❓ 为什么
let变量不能提前访问,会报Cannot access before initialization?
这些问题的背后,正是 JavaScript 变量声明机制的"暗流"------变量提升 、暂时性死区 、作用域规则。
今天,我们就来揭开 var、let、const 的神秘面纱,带你彻底搞懂 JavaScript 的变量声明机制。
🧩 一、JavaScript 中如何声明变量?
JavaScript 提供了三种声明变量的方式:
| 关键字 | 用途 | 特性 |
|---|---|---|
var |
声明变量(ES5 及以前) | ❌ 变量提升、函数作用域、可重复声明 |
let |
声明块级变量(ES6) | ✅ 块级作用域、无变量提升(有暂时性死区)、不可重复声明 |
const |
声明常量(ES6) | ✅ 块级作用域、必须初始化、不可重新赋值 |
💡 推荐 :
ES6 之后,建议不再使用var,优先使用let和const。
📦 二、var:被时代淘汰的"糟粕"
1. 变量提升(Hoisting)
var 声明的变量会在编译阶段被提升到作用域顶部。
ini
javascript
编辑
console.log(age); // 输出: undefined
var age = 18;
执行过程解析:
javascript
javascript
编辑
// 实际执行顺序(编译阶段已提升)
var age; // 变量提升,值为 undefined
console.log(age); // undefined
age = 18; // 赋值
⚠️ 问题 :
这种"先使用后声明"的行为不符合直觉,容易导致 bug。
2. 函数提升(Function Hoisting)
函数声明也会被提升,且函数体也会被提升。
scss
javascript
编辑
setWidth(); // 正常执行
function setWidth() {
var width = 100;
console.log(width); // 输出: 100
}
对比:函数表达式不会完全提升
scss
javascript
编辑
setHeight(); // TypeError: setHeight is not a function
var setHeight = function() {
console.log(200);
};
📌 核心区别:
function fn() {}:函数声明 → 声明 + 赋值 都提升var fn = function() {}:函数表达式 → 只有声明提升
🔒 三、let 和 const:ES6 的救星
1. 块级作用域(Block Scope)
let 和 const 只在 {} 块内有效。
ini
javascript
编辑
{
let a = 1;
const b = 2;
}
console.log(a); // ReferenceError: a is not defined
✅ 优势:避免变量污染全局作用域。
2. 暂时性死区(Temporal Dead Zone, TDZ)
在 let/const 声明之前访问变量,会报错。
ini
javascript
编辑
console.log(PI); // ReferenceError: Cannot access 'PI' before initialization
const PI = 3.1415926;
💡 为什么设计 TDZ?
防止开发者误用"提升"特性,提高代码可读性和安全性。
3. const 的"常量"真相
const 并不意味着"值不可变",而是引用不可变。
✅ 简单类型:不可修改
ini
javascript
编辑
const key = 'abc123';
key = 'abc234'; // TypeError: Assignment to constant variable.
✅ 复杂类型:可修改属性
ini
javascript
编辑
const person = {
name: "ysw",
age: 28,
};
person.age = 21; // ✅ 允许,修改的是对象内部属性
console.log(person); // { name: "ysw", age: 21 }
person = {}; // ❌ 报错,不能重新赋值
🔒 如何真正"冻结"对象?
ini
javascript
编辑
const wes = Object.freeze(person);
wes.age = 17; // ❌ 在严格模式下报错,非严格模式静默失败
console.log(wes); // age 仍为 21
📌 Object.freeze() :浅冻结,只冻结对象自身属性,不递归冻结嵌套对象。
🧠 四、三大错误类型解析
| 错误 | 原因 | 示例 |
|---|---|---|
ReferenceError: height is not defined |
变量未声明,作用域外调用 | console.log(height);(未声明) |
TypeError: Assignment to constant variable. |
尝试修改 const 变量 |
const a = 1; a = 2; |
ReferenceError: Cannot access 'PI' before initialization |
访问了暂时性死区中的变量 | console.log(PI); const PI = 3.14; |
🖼️ 五、图解:变量声明机制
📌 关键记忆:
var:提升 → 赋值 → 访问let/const:提升 → TDZ → 赋值 → 访问
💼 六、大厂高频面试题(变量声明专项)
❓ Q1: var、let、const 有什么区别?
✅ 参考回答:
| 特性 | var |
let |
const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | ✅ | ❌(有 TDZ) | ❌(有 TDZ) |
| 重复声明 | ✅ | ❌ | ❌ |
| 重新赋值 | ✅ | ✅ | ❌ |
📌 总结:
var是历史遗留,let用于变量,const用于常量。
❓ Q2: 什么是暂时性死区(TDZ)?为什么设计它?
✅ 参考回答 :
暂时性死区是指 let/const 变量从作用域开始到声明语句之间的区域。在此区域内访问变量会报错。
设计目的:
- 避免
var提升带来的"先使用后声明"陷阱 - 提高代码可读性和安全性
- 强制开发者遵循"先声明后使用"的良好习惯
❓ Q3: const 定义的对象属性能改吗?如何真正冻结?
✅ 参考回答 :
可以。const 只保证引用地址不变,不保证对象内部属性不变。
使用 Object.freeze(obj) 可以冻结对象,使其属性不可修改。但它是浅冻结,嵌套对象仍可修改。
ini
javascript
编辑
const obj = { a: { b: 1 } };
Object.freeze(obj);
obj.a.b = 2; // ✅ 仍可修改
💡 加分项:可用
deepFreeze实现深冻结。
❓ Q4: function 和 var 的提升有什么区别?
✅ 参考回答:
var:只提升声明,不提升赋值function:提升声明和函数体(函数声明)- 函数表达式:行为类似
var,只提升声明
scss
javascript
编辑
foo(); // TypeError
var foo = function() {};
bar(); // 正常执行
function bar() {}
❓ Q5: 为什么建议不再使用 var?
✅ 参考回答:
- 变量提升导致"先使用后声明",易出 bug
- 函数作用域容易污染全局变量
- 可重复声明增加维护成本
let/const提供了更安全、更清晰的块级作用域
📌 现代开发应优先使用
let和const。
✅ 七、总结:变量声明最佳实践
| 场景 | 推荐 | 说明 |
|---|---|---|
| 普通变量 | let |
块级作用域,安全 |
| 常量 | const |
防止意外修改 |
| 对象/数组 | const + Object.freeze()(如需完全冻结) |
保证引用和内容不变 |
| 循环变量 | let |
避免闭包问题 |
| 全局变量 | 尽量避免 | 使用模块化或立即执行函数 |
🌟 记住口诀 :
"能用const不用let,能用let不用var"
"先声明,后使用,TDZ 拒绝提前访问"
🚀 下一步
- ✅ 实践:用
let/const重构旧项目中的var - ✅ 演练:故意触发 TDZ 错误,理解其机制
- ✅ 进阶:学习
Object.freeze()和深冻结实现
💡 JavaScript 的"糟粕"已被淘汰,拥抱 ES6+ 的"精华" ,写出更安全、更可维护的代码! ✨