从 var 到 let/const:JavaScript 变量声明的进化之路
JavaScript 最初设计于 1995 年,仅用 10 天完成原型。早期的 JS 主要用于网页简单的交互(如表单验证、动态显示),功能简单,语言设计也存在诸多"缺陷"或"不合理之处"。Douglas Crockford 在其著作《JavaScript: The Good Parts》中曾将 JS 分为三部分:
- The Good(好的部分)
- The Bad Parts(糟粕)
- The Awful(极差的部分)
其中,var 的变量提升就被归为"Bad Parts"之一。
随着 ES6(ECMAScript 2015)的发布,JavaScript 正式迈向现代化,引入了 let 和 const,解决了 var 带来的诸多问题,使 JS 更适合大型项目和企业级开发。
本文将带你深入理解:
var的缺陷:变量提升与作用域问题let与const的优势- 暂时性死区(Temporal Dead Zone)
- 常量与对象冻结
- 块级作用域的重要性
一、var:曾经的唯一选择,如今的"历史遗留"
在 ES6 之前,JavaScript 唯一的变量声明方式是 var。
ini
var age = 18;
age++;
var PI = 3.1415926; // 约定大写表示"常量",但实际可变
console.log(age); // 19
PI = 3.15;
console.log(PI); // 3.15 ------ 居然可以修改!
虽然我们习惯用大写字母命名"不应改变"的值(如 PI),但这只是编程约定 ,JavaScript 并不强制。这意味着 var 无法真正声明"常量"。
❌ var 的两大问题
1. 变量提升(Hoisting)
JS 是解释型脚本语言,但也有"编译阶段" ------ 在代码执行前的一瞬间,JS 引擎会扫描所有 var 声明,并将其"提升"到当前作用域顶部。
ini
console.log(age); // undefined
var age = 18;
这段代码的执行过程实际上是:
ini
var age; // 提升声明
console.log(age); // undefined(未赋值)
age = 18; // 执行赋值
变量被"提前访问",但值是 undefined,容易引发难以排查的 bug。
2. 缺乏块级作用域
var 只有函数作用域和全局作用域,不支持块级作用域({})。
ini
{
var age = 18;
let height = 188;
}
console.log(age); // 18 ------ var 跨出了块
console.log(height); // ReferenceError: height is not defined
这导致在 for 循环、if 判断等块中声明的 var 变量会"泄露"到外部,破坏代码的封装性。
二、ES6 的救赎:let 和 const
ES6(2015 年发布)为 JavaScript 带来了现代化语法,其中最重要的改进之一就是引入了 let 和 const。
✅ 推荐:从此告别 var,使用 let 和 const
ini
let height = 188;
height++; // 允许修改
console.log(height); // 189
const key = 'abc123';
key = 'abc1234'; // TypeError: Assignment to constant variable.
let:声明可变变量const:声明常量(一旦赋值不可重新赋值)
💡 最佳实践:默认使用
const,只有需要重新赋值时才用let。
三、let/const 的核心优势
1. 消除变量提升:引入"暂时性死区"(Temporal Dead Zone)
let 和 const 不会像 var 那样提升到作用域顶部。在声明之前访问它们会抛出错误。
ini
console.log(height); // ReferenceError: Cannot access 'height' before initialization
let height = 188;
这个从作用域开始到变量声明之间的区域,被称为"暂时性死区"(TDZ)。它强制开发者遵循"先声明后使用"的逻辑,提高了代码的可读性和安全性。
2. 支持块级作用域
let 和 const 在 {} 块内声明时,只在该块内有效。
ini
{
let height = 188;
var age = 18;
}
console.log(age); // 18
console.log(height); // ReferenceError: height is not defined
这使得变量的作用范围更加精确,避免了命名冲突,是大型项目中模块化开发的基础。
四、const 的真相:不是"值不可变",而是"引用不可变"
很多初学者误以为 const 声明的变量完全不能修改。其实,const 保证的是变量绑定的引用地址不能改变 ,但引用内部的内容可以修改。
ini
const person = {
name: "yxw",
age: 28
};
// person = "hahaha"; // ❌ 报错:Cannot assign to const variable
person.age = 21; // ✅ 允许:修改对象内部属性
console.log(person); // { name: "yxw", age: 21 }
如何让对象真正"不可变"?
使用 Object.freeze() 冻结对象:
ini
const wes = Object.freeze(person);
wes.age = 17; // ❌ 在严格模式下会报错,非严格模式静默失败
console.log(wes); // 仍然是 { name: "yxw", age: 21 }
Object.freeze() 可以防止对象属性被添加、删除或修改,实现真正的不可变性(shallow freeze,浅冻结)。
五、函数提升 vs 变量提升
JavaScript 中,函数是一等公民,函数声明也会被提升。
scss
setWidth(); // ✅ 正常执行
function setWidth() {
var width = 100;
console.log(width);
}
函数提升与 var 不同:函数声明会连同函数体一起提升 ,而 var 只提升声明,不提升赋值。
但注意:函数表达式不会完全提升:
scss
getLength(); // ❌ TypeError: getLength is not a function
var getLength = function() {
console.log(100);
};
六、常见错误汇总
| 错误类型 | 示例 | 原因 |
|---|---|---|
ReferenceError: height is not defined |
在作用域外访问 let/const 变量 |
超出作用域范围 |
TypeError: Assignment to constant variable |
修改 const 声明的变量 |
const 不允许重新赋值 |
ReferenceError: Cannot access 'height' before initialization |
在 let/const 声明前访问 |
暂时性死区(TDZ) |
七、总结:现代 JavaScript 变量声明的最佳实践
| 特性 | var |
let |
const |
|---|---|---|---|
| 支持块级作用域 | ❌ | ✅ | ✅ |
| 变量提升 | ✅(声明提升) | ❌(TDZ) | ❌(TDZ) |
| 可重新赋值 | ✅ | ✅ | ❌ |
| 推荐使用 | ❌(废弃) | ✅(可变变量) | ✅✅✅(默认选择) |
✅ 使用建议:
- 永远不要再使用
var,它是历史的糟粕。 - 优先使用
const,只有需要重新赋值时才用let。 - 理解暂时性死区,避免在声明前访问变量。
- 用
Object.freeze()实现对象不可变,提升数据安全性。 - 利用块级作用域,让变量生命周期更清晰。
结语
从 var 到 let/const,不仅是语法的更新,更是 JavaScript 语言成熟化的标志。它让 JS 从"网页脚本语言"蜕变为支持大型项目、企业级开发的现代化编程语言。
"好的代码,始于正确的变量声明。"
------ 每一位现代 JavaScript 开发者
现在,就从你的下一个项目开始,彻底告别 var,拥抱 let 与 const 吧!
📌 附录:快速对照表
| 场景 | 推荐写法 |
|---|---|
| 声明一个会变化的变量 | let count = 0; |
| 声明一个常量(如配置、ID) | const API_KEY = 'xxx'; |
| 声明一个不可变对象 | const config = Object.freeze({...}); |
| 避免变量提升陷阱 | 使用 let/const,不在声明前访问 |