📚 JavaScript 变量声明三剑客:`var`、`let`、`const` 学习笔记


🌟 前言:你不知道的 JavaScript 小知识,你搞明白了吗?

"JavaScript 是一门灵活的语言,但也正因为它的'宽容',埋下了许多陷阱。"

------《你不知道的 JavaScript》

你是否遇到过:

  • ❓ 为什么变量还没定义就能访问,值却是 undefined
  • ❓ 为什么 const 定义的对象,属性还能改?
  • ❓ 为什么 let 变量不能提前访问,会报 Cannot access before initialization

这些问题的背后,正是 JavaScript 变量声明机制的"暗流"------变量提升暂时性死区作用域规则

今天,我们就来揭开 varletconst 的神秘面纱,带你彻底搞懂 JavaScript 的变量声明机制。


🧩 一、JavaScript 中如何声明变量?

JavaScript 提供了三种声明变量的方式:

关键字 用途 特性
var 声明变量(ES5 及以前) ❌ 变量提升、函数作用域、可重复声明
let 声明块级变量(ES6) ✅ 块级作用域、无变量提升(有暂时性死区)、不可重复声明
const 声明常量(ES6) ✅ 块级作用域、必须初始化、不可重新赋值

💡 推荐
ES6 之后,建议不再使用 var ,优先使用 letconst


📦 二、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() {}:函数表达式 → 只有声明提升

🔒 三、letconst:ES6 的救星

1. 块级作用域(Block Scope)

letconst 只在 {} 块内有效。

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: varletconst 有什么区别?

参考回答

特性 var let const
作用域 函数作用域 块级作用域 块级作用域
变量提升 ❌(有 TDZ) ❌(有 TDZ)
重复声明
重新赋值

📌 总结:var 是历史遗留,let 用于变量,const 用于常量。


❓ Q2: 什么是暂时性死区(TDZ)?为什么设计它?

参考回答

暂时性死区是指 let/const 变量从作用域开始到声明语句之间的区域。在此区域内访问变量会报错。

设计目的

  1. 避免 var 提升带来的"先使用后声明"陷阱
  2. 提高代码可读性和安全性
  3. 强制开发者遵循"先声明后使用"的良好习惯

❓ Q3: const 定义的对象属性能改吗?如何真正冻结?

参考回答

可以。const 只保证引用地址不变,不保证对象内部属性不变。

使用 Object.freeze(obj) 可以冻结对象,使其属性不可修改。但它是浅冻结,嵌套对象仍可修改。

ini 复制代码
javascript
编辑
const obj = { a: { b: 1 } };
Object.freeze(obj);
obj.a.b = 2; // ✅ 仍可修改

💡 加分项:可用 deepFreeze 实现深冻结。


❓ Q4: functionvar 的提升有什么区别?

参考回答

  • var:只提升声明,不提升赋值
  • function:提升声明和函数体(函数声明)
  • 函数表达式:行为类似 var,只提升声明
scss 复制代码
javascript
编辑
foo(); // TypeError
var foo = function() {};

bar(); // 正常执行
function bar() {}

❓ Q5: 为什么建议不再使用 var

参考回答

  1. 变量提升导致"先使用后声明",易出 bug
  2. 函数作用域容易污染全局变量
  3. 可重复声明增加维护成本
  4. let/const 提供了更安全、更清晰的块级作用域

📌 现代开发应优先使用 letconst


✅ 七、总结:变量声明最佳实践

场景 推荐 说明
普通变量 let 块级作用域,安全
常量 const 防止意外修改
对象/数组 const + Object.freeze()(如需完全冻结) 保证引用和内容不变
循环变量 let 避免闭包问题
全局变量 尽量避免 使用模块化或立即执行函数

🌟 记住口诀
"能用 const 不用 let,能用 let 不用 var"
"先声明,后使用,TDZ 拒绝提前访问"


🚀 下一步

  • ✅ 实践:用 let/const 重构旧项目中的 var
  • ✅ 演练:故意触发 TDZ 错误,理解其机制
  • ✅ 进阶:学习 Object.freeze() 和深冻结实现

💡 JavaScript 的"糟粕"已被淘汰,拥抱 ES6+ 的"精华" ,写出更安全、更可维护的代码! ✨

相关推荐
茶憶2 分钟前
UniApp RenderJS中集成 Leaflet地图,突破APP跨端开发限制
javascript·vue.js·uni-app
没头脑和不高兴y15 分钟前
Element-Plus-X:基于Vue 3的AI交互组件库
前端·javascript
ErMao17 分钟前
Proxy 与 Reflect:最硬核、最实用的解释
前端·javascript
k093319 分钟前
在组件外(.js文件)中使用pinia的方法2--在http.js中使用pinia
开发语言·javascript·http
Glommer32 分钟前
AST 反混淆处理示例
javascript·爬虫
二川bro34 分钟前
第44节:物理引擎进阶:Bullet.js集成与高级物理模拟
开发语言·javascript·ecmascript
越努力越幸运50838 分钟前
JavaScript进阶篇垃圾回收、闭包、函数提升、剩余参数、展开运算符、对象解构
开发语言·javascript
程序员ys1 小时前
Vue的响应式系统是怎么实现的
前端·javascript·vue.js
aduzhe1 小时前
关于在嵌入式中打印float类型遇到的bug
前端·javascript·bug
鹏多多1 小时前
vue过滤器filter的详解及和computed的区别
前端·javascript·vue.js