JavaScript变量三剑客:var、let、const 的爱恨情仇,你真的懂了吗?
还记得你第一次写 JavaScript 的时候吗?
是不是也像我一样,傻乎乎地写着:
console.log(age); // undefined?
var age = 18;
心里默默嘀咕:“我还没赋值呢,怎么不报错反而输出 undefined?”
别急,这不是你的问题,是 JavaScript 的“历史遗留问题”在作祟。今天,我们就来聊聊 JS 中最基础却又最容易被误解的三个关键字——var、let、const。
它们就像三位性格迥异的老友:一个“老顽固”,一个“新锐派”,一个“铁面无私”。而他们的恩怨情仇,正是现代 JavaScript 演进史的缩影。
一、var:那个“总想提前登场”的老大哥
var 是 JavaScript 最原始的变量声明方式,从 ES1 就存在了。但它有个让人抓狂的特点:变量提升(Hoisting)。
1. 变量提升:编译阶段就“出名”
来看一段“迷惑行为大赏”:
console.log(name); // 输出:undefined
var name = "张三";
你没看错,代码还没执行到 var name,却能访问它!
但值是 undefined,不是 "张三"。
为什么?因为 JavaScript 在编译阶段就把所有 var 声明“提升”到了作用域顶部,相当于:
var name; // 提升了声明
console.log(name); // undefined
name = "张三"; // 执行阶段才赋值
这就导致了一个严重问题:代码可读性差,容易出 bug。
更离谱的是,var 还不支持块级作用域!
for (var i = 0; i < 3; i++) {
console.log(i);
}
console.log(i); // 居然能打印出 3!
i 竟然跑出了 for 循环的 {} 块!因为在 JS 看来,var 只认函数作用域,不认块级作用域。
💡 小结:
var是“全局混子”,变量提升 + 无块级作用域,容易引发命名冲突和逻辑混乱。建议:能不用就不用!
二、let:新时代的“守序良民”
ES6(2015年)带来了革命性更新,let 和 const 横空出世。它们的核心使命就是:解决 var 的糟粕。
1. 告别变量提升?不,是“暂时性死区”
let 不会像 var 那样“偷偷提升”。如果你提前访问,JS 会直接给你一记耳光:
console.log(height); // ❌ 报错!
let height = 188;
错误信息是:
ReferenceError: Cannot access 'height' before initialization
这叫 暂时性死区(Temporal Dead Zone, TDZ) —— 从块开始到 let 声明前的区域,变量存在但不可访问。
✅ 好处:强制你“先声明,后使用”,符合编程直觉,减少低级错误。
2. 支持块级作用域,终于“有边界”了
{
let age = 18;
var name = "李四";
}
console.log(name); // ✅ 李四(var 无块级作用域)
console.log(age); // ❌ 报错!age is not defined
从此,{} 真正成了“作用域围墙”,变量不再乱窜。
3. 不允许重复声明
let a = 1;
let a = 2; // ❌ SyntaxError: Identifier 'a' has already been declared
比 var 安全多了(var 可以重复 var a,虽然不推荐)。
💡 小结:
let是现代 JS 的推荐选择,用于声明会改变的变量。它守规矩、有边界、不越界,是大型项目的“中流砥柱”。
三、const:说一不二的“铁血战士”
如果说 let 是“可以改的变量”,那 const 就是“立下的誓言”。
1. 常量声明,一旦声明,终身不变
const PI = 3.1415926;
PI = 3.14; // ❌ TypeError: Assignment to constant variable.
JS 直接报错,绝不含糊。
2. 注意!const 不是“值不能变”,而是“引用不能变”
来看这个经典陷阱:
const person = {
name: "ysw",
age: 28
};
person.age = 21; // ✅ 合法!对象内部属性可以改
console.log(person); // { name: "ysw", age: 21 }
person = {}; // ❌ 报错!不能重新赋值
为什么?因为 const 锁住的是变量指向的内存地址,而不是地址里的内容。
- 对于基本类型(string、number、boolean):值和地址绑定,所以真·不变。
- 对于复杂类型(object、array、function):只能保证引用不变,但内容可改。
3. 如何让对象彻底“冻结”?
如果真想让一个对象“一丝不动”,可以用 Object.freeze():
const wes = Object.freeze({
name: "wes",
age: 17
});
wes.age = 20; // ⚠️ 严格模式下报错,非严格模式静默失败
console.log(wes); // { name: "wes", age: 17 } —— 没变!
💡 小结:
const用于声明不会重新赋值的变量。优先使用const,除非你明确知道变量会变,再用let。
四、终极对比表:谁才是你的最佳拍档?
| 特性 | var | let | const |
|---|---|---|---|
| 是否变量提升 | ✅ 是(只提升声明) | ❌ 否(有暂时性死区) | ❌ 否 |
| 块级作用域 | ❌ 不支持 | ✅ 支持 | ✅ 支持 |
| 函数作用域 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| 可重复声明 | ✅ 可以(不推荐) | ❌ 不行 | ❌ 不行 |
| 可重新赋值 | ✅ 可以 | ✅ 可以 | ❌ 不行 |
| 推荐使用场景 | ❌ 废弃 | ✅ 会变的变量 | ✅ 默认首选 |
✅ 最佳实践:
- 默认用
const- 如果需要重新赋值,改用
let- 永远不要用
var
五、常见报错 & 避坑指南
1. ReferenceError: Cannot access 'xxx' before initialization
console.log(height);
let height = 188;
👉 原因:let/const 的暂时性死区。
✅ 解法:确保先声明再使用。
2. TypeError: Assignment to constant variable
const key = 'abc123';
key = 'abc234'; // ❌
👉 原因:试图修改 const 声明的变量。
✅ 解法:换 let,或接受“它就是不变的”。
3. ReferenceError: xxx is not defined
console.log(username); // 拼错了 or 忘声明
👉 原因:变量根本不存在。
✅ 解法:检查拼写,确认是否声明。
六、彩蛋:函数也是“提升党”?
有趣的是,函数声明也会提升,而且比 var 更“激进”:
sayHello(); // ✅ 能执行!
function sayHello() {
console.log("Hello!");
}
函数声明不仅提升声明,还连定义一起提升(函数提升)。
但注意,函数表达式不行:
sayHi(); // ❌ TypeError: sayHi is not a function
var sayHi = function() {
console.log("Hi!");
};
因为它本质是 var 声明 + 匿名函数,只提升声明,不提升赋值。
结语:从“脚本玩具”到“企业级语言”
JavaScript 早期只是个“页面小助手”,用来弹个 alert、改个颜色。但随着 ES6 的到来,let 和 const 的引入,标志着 JS 正式迈向工程化、规范化。
var是过去,let/const是未来。
我们学习这些细节,不是为了炫技,而是为了让代码更清晰、更安全、更适合团队协作。
下次写代码时,不妨问自己一句:
“这个变量,我真的需要改变它吗?”
如果不需要,那就用const吧——给你的代码加一把锁,也给未来的自己一份安心。
📌 互动时间:
你在项目中还在用 var 吗?遇到过哪些 let/const 的坑?欢迎在评论区分享你的故事!
💬 点赞 + 收藏 + 关注,解锁更多前端进阶干货!