从 var 到 let/const:理解 JavaScript ES6 变量声明的演进

3 阅读6分钟

从 var 到 let/const:理解 JavaScript ES6 变量声明的演进

JavaScript 诞生于 1995 年,据说布兰登·艾克只用了一周时间就设计出了这门语言的最初版本。它最初的目的很简单——给网页添加一些交互能力,做做幻灯片效果、操作一下 DOM。谁也没想到,二十多年后,这门"浏览器的副产品"会成为世界上使用最广泛的编程语言之一。

正是因为"赶工"的出身,JavaScript 早期留下了一些设计上的瑕疵。var 关键字就是其中之一。2015 年,ES6(ECMAScript 2015)带来了 letconst,标志着 JavaScript 走向成熟,开始能够支撑企业级大型项目的开发。

本文将从作用域、变量提升、常量的意义以及经典面试题入手,梳理这一演进背后的逻辑。


一、老将 var:不拘小节的"散漫派"

var 是 JavaScript 的"开国元勋",1995 年一周速成的产物。它的特点一目了然:

var 的三大特点

  • 作用域:只认函数,不认大括号。  在 iffor 等代码块里用 var 声明变量,它会若无其事地"穿透"大括号,跑到外面的函数作用域或全局作用域里去。江湖人称"块级作用域无视者"。
  • 可以重复声明:  同一个变量名声明两次,var 眼都不眨一下,后面的直接覆盖前面的,连个警告都不给。
  • 有变量提升(hoisting):  声明会被悄悄提到作用域顶部,但赋值原地不动。于是你可以在赋值之前访问它,得到一个神秘的 undefined——代码先有蛋还是先有鸡?var 说:"都有,只不过蛋是 undefined。"
console.log(pizza);  // undefined —— 没报错,但也没东西
var pizza = 'Deep Dish';

编译阶段,引擎看到 var pizza,先给它占个座,默认值 undefined。等到执行阶段跑到了 = 那一行,才把 'Deep Dish' 端上来。这就叫变量提升——和你写代码的顺序对着干,容易悄悄埋坑。


二、新人 let:守规矩的"模范生"

let 是 ES6 推出的"改进版"变量声明,专门来给 var 收拾烂摊子。

let 的三大特点

  • 作用域:严格遵循块级作用域。  大括号就是它的边界,绝不越界半步。iffor、甚至光秃秃的 { } 都能圈住它。函数执行完毕或代码块跑完,变量就被垃圾回收,生命结束,干净利落。
  • 不可重复声明:  同一个作用域里,同一变量名报两次 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 来了,letconst 就是给老语言动的一场"外科手术"。


五、一张表格看清三兄弟

特性var(老将)let(新人)const(大佬)
作用域函数作用域块级作用域块级作用域
可重复声明✅ 可以❌ 不行❌ 不行
变量提升有(undefined)暂时性死区暂时性死区
值可修改可以可以简单类型不行,复杂类型内容可改
声明时不赋值可以可以必须赋值

六、现代 JavaScript 生存指南

一句话原则:

默认const,需要改就换let,忘了var

另外提一嘴:JavaScript 是弱类型动态语言,变量的类型由值决定。let 允许你把一个变量从布尔值改成字符串:

let winner = false;
winner = '张';     // 语法允许,但别这么干

虽然语法不会管你,但在同一个变量上来回切换类型,就像一个人今天说自己叫张三,明天说自己叫李四——程序不会报错,但维护代码的同事会想打人。


理解了这三个关键字,你就拿到了在 JavaScript 作用域世界里自由穿行的地图。从此,var 的诡异行为不再让你挠头,let 的块级作用域是你的利器,const 给你的数据加了一道安全锁。

前朝旧事随风去,let const 定江山。🚀