从 var 到 let/const:理解 JavaScript ES6 变量声明的演进
JavaScript 诞生于 1995 年,据说布兰登·艾克只用了一周时间就设计出了这门语言的最初版本。它最初的目的很简单——给网页添加一些交互能力,做做幻灯片效果、操作一下 DOM。谁也没想到,二十多年后,这门"浏览器的副产品"会成为世界上使用最广泛的编程语言之一。
正是因为"赶工"的出身,JavaScript 早期留下了一些设计上的瑕疵。var 关键字就是其中之一。2015 年,ES6(ECMAScript 2015)带来了 let 和 const,标志着 JavaScript 走向成熟,开始能够支撑企业级大型项目的开发。
本文将从作用域、变量提升、常量的意义以及经典面试题入手,梳理这一演进背后的逻辑。
一、老将 var:不拘小节的"散漫派"
var 是 JavaScript 的"开国元勋",1995 年一周速成的产物。它的特点一目了然:
var 的三大特点
- 作用域:只认函数,不认大括号。 在
if、for等代码块里用var声明变量,它会若无其事地"穿透"大括号,跑到外面的函数作用域或全局作用域里去。江湖人称"块级作用域无视者"。 - 可以重复声明: 同一个变量名声明两次,
var眼都不眨一下,后面的直接覆盖前面的,连个警告都不给。 - 有变量提升(hoisting): 声明会被悄悄提到作用域顶部,但赋值原地不动。于是你可以在赋值之前访问它,得到一个神秘的
undefined——代码先有蛋还是先有鸡?var说:"都有,只不过蛋是 undefined。"
console.log(pizza); // undefined —— 没报错,但也没东西
var pizza = 'Deep Dish';
编译阶段,引擎看到 var pizza,先给它占个座,默认值 undefined。等到执行阶段跑到了 = 那一行,才把 'Deep Dish' 端上来。这就叫变量提升——和你写代码的顺序对着干,容易悄悄埋坑。
二、新人 let:守规矩的"模范生"
let 是 ES6 推出的"改进版"变量声明,专门来给 var 收拾烂摊子。
let 的三大特点
- 作用域:严格遵循块级作用域。 大括号就是它的边界,绝不越界半步。
if、for、甚至光秃秃的{ }都能圈住它。函数执行完毕或代码块跑完,变量就被垃圾回收,生命结束,干净利落。 - 不可重复声明: 同一个作用域里,同一变量名报两次
let,直接报错。杜绝了"不小心覆盖"的隐患。 - 没有变量提升(有暂时性死区): 在声明之前访问它,直接抛出
ReferenceError: Cannot access 'pizza' before initialization。这让代码行为和你阅读的顺序保持一致——从上到下,先声明后使用,符合直觉。
用 let 干掉一道经典面试题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// 输出:3, 3, 3 —— 懵逼三连
为什么会这样?var 不认块级作用域,循环结束后只有一个 i,值是 3。三个 setTimeout 回调都引用同一个 i,自然全打印 3。
把 var 换成 let:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// 输出:0, 1, 2 —— 岁月静好
let 让每次迭代都拥有独立的 i,各有各的值,井水不犯河水。每个 setTimeout 都记住了自己那一轮的 i,这就叫"闭包遇上块级作用域,如鱼得水"。
三、大佬 const:雷打不动的"守财奴"
const 是三个人里最"固执"的那个——声明的东西,打死也不让改。
const 的四大特点
-
声明时必须立刻赋值: 不能像
let那样搞"先占坑后填值"的花招。const说:"你想好值了吗?想好了再告诉我,别磨叽。" -
简单数据类型,值不能改: 数字、字符串、布尔值,一旦绑定,永远绑定。
const key = 'abc123'; key = 'ABC123'; // TypeError: Assignment to constant variable -
复杂数据类型,引用不能改,内容可以改: 对象和数组,
const锁住的是变量名和内存地址的绑定。"孙悟空的金箍棒可以变大变小,但你不能把它换成九齿钉耙。"const person = { name: '张锦', age: 18 }; person.age++; // ✅ 改内部属性,完全可以 console.log(person); // { name: '张锦', age: 19 } person = '111'; // ❌ 改引用,不答应 -
块级作用域 + 无变量提升: 和
let一样,守规矩,不越界,不偷跑。
为什么要有 const?
ES5 时代,"常量"全靠默契:
var PI = 3.1415926;
var API_KEY = 'sk-xxx'; // 全大写 = 大家别改我,求你了
你说万一谁手滑写了 PI = 3?代码不会报错,但圆周率从此变成了 3。const 把"约定"变成了"强制"——语言层面给你兜底。
四、一门"一周速成"的语言
为什么 var 会留下这么多坑?这就不得不提 JavaScript 的身世了。
1995 年,网景公司觉得浏览器应该能跑点小脚本,于是让布兰登·艾克搞一个。布兰登同志加班一周,把 Java 的语法、Scheme 的函数式思想、Self 的原型继承三样东西搅在一起,JavaScript 就这么"拼凑"出来了。它最初的名字叫 LiveScript,后来蹭 Java 的热度改成了 JavaScript——虽然它和 Java 的关系,大概就像雷锋和雷峰塔。
因为赶工,var 的块级作用域没来得及做,变量提升也没来得及优化。这些问题在当时"写个幻灯片、弹个对话框"的场景下无伤大雅。但当 JavaScript 开始承担企业级大型项目时,坑就填不平了。
于是 2015 年 ES6 来了,let 和 const 就是给老语言动的一场"外科手术"。
五、一张表格看清三兄弟
| 特性 | var(老将) | let(新人) | const(大佬) |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 可重复声明 | ✅ 可以 | ❌ 不行 | ❌ 不行 |
| 变量提升 | 有(undefined) | 暂时性死区 | 暂时性死区 |
| 值可修改 | 可以 | 可以 | 简单类型不行,复杂类型内容可改 |
| 声明时不赋值 | 可以 | 可以 | 必须赋值 |
六、现代 JavaScript 生存指南
一句话原则:
默认
const,需要改就换let,忘了var。
另外提一嘴:JavaScript 是弱类型动态语言,变量的类型由值决定。let 允许你把一个变量从布尔值改成字符串:
let winner = false;
winner = '张'; // 语法允许,但别这么干
虽然语法不会管你,但在同一个变量上来回切换类型,就像一个人今天说自己叫张三,明天说自己叫李四——程序不会报错,但维护代码的同事会想打人。
理解了这三个关键字,你就拿到了在 JavaScript 作用域世界里自由穿行的地图。从此,var 的诡异行为不再让你挠头,let 的块级作用域是你的利器,const 给你的数据加了一道安全锁。
前朝旧事随风去,let const 定江山。🚀