你写的JS代码总出bug?可能是var在偷偷搞事情!

13 阅读5分钟

JavaScript变量三剑客:varletconst 的爱恨情仇,你真的懂了吗?


还记得你第一次写 JavaScript 的时候吗?
是不是也像我一样,傻乎乎地写着:

console.log(age); // undefined?
var age = 18;

心里默默嘀咕:“我还没赋值呢,怎么不报错反而输出 undefined?

别急,这不是你的问题,是 JavaScript 的“历史遗留问题”在作祟。今天,我们就来聊聊 JS 中最基础却又最容易被误解的三个关键字——varletconst

它们就像三位性格迥异的老友:一个“老顽固”,一个“新锐派”,一个“铁面无私”。而他们的恩怨情仇,正是现代 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年)带来了革命性更新,letconst 横空出世。它们的核心使命就是:解决 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


四、终极对比表:谁才是你的最佳拍档?

特性varletconst
是否变量提升✅ 是(只提升声明)❌ 否(有暂时性死区)❌ 否
块级作用域❌ 不支持✅ 支持✅ 支持
函数作用域✅ 支持✅ 支持✅ 支持
可重复声明✅ 可以(不推荐)❌ 不行❌ 不行
可重新赋值✅ 可以✅ 可以❌ 不行
推荐使用场景❌ 废弃✅ 会变的变量✅ 默认首选

最佳实践

  • 默认用 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 的到来,letconst 的引入,标志着 JS 正式迈向工程化、规范化

var 是过去,let/const 是未来。

我们学习这些细节,不是为了炫技,而是为了让代码更清晰、更安全、更适合团队协作。

下次写代码时,不妨问自己一句:

“这个变量,我真的需要改变它吗?”
如果不需要,那就用 const 吧——给你的代码加一把锁,也给未来的自己一份安心。


📌 互动时间
你在项目中还在用 var 吗?遇到过哪些 let/const 的坑?欢迎在评论区分享你的故事!

💬 点赞 + 收藏 + 关注,解锁更多前端进阶干货!