ES6 核心特性完全指南:从变量声明到作用域解析
告别 var 的坑,拥抱 let 和 const 的现代 JavaScript 开发
前言
1995年,Brendan Eich 只用了10天就设计出了 JavaScript。这个“一周 KPI 项目”如今已成为全球最流行的编程语言之一。早期设计的一些“瑕疵”一直困扰着开发者,直到 ES6(ECMAScript 2015)的出现,JavaScript 才真正迈入了企业级开发的时代。
今天,我们就来深入理解 ES6 中最重要的变量声明和作用域机制。
一、从 var 到 let/const 的进化
1.1 var 的历史问题
在 ES5 时代,我们只能用 var 声明变量:
var name = "JavaScript";
var PI = 3.1415926;
var CHAT_MODEL = "deepseek-chat";
var 的主要问题:不支持块级作用域。
1.2 let 和 const 的优势
ES6 引入了两个新的声明方式:
- let:声明变量,值可以改变
- const:声明常量,值不可改变(针对简单数据类型)
// let 的使用
let points = 50;
points = 51; // ✅ 可以重新赋值
points = "51"; // ⚠️ 可以改变类型(但不推荐)
// const 的使用
const KEY = 'abc123';
KEY = 'ABC123'; // ❌ TypeError: Assignment to constant variable
// const 必须在声明时赋值
const GENDER; // ❌ SyntaxError: Missing initializer
1.3 复杂数据类型与 const
对于对象和数组等复杂数据类型,const 只保证引用不变,内部属性可以修改:
const person = {
name: '马牛逼',
age: 18
};
person.age++; // ✅ 可以修改属性
console.log(person); // { name: '马牛逼', age: 19 }
person = "111"; // ❌ 不能重新赋值
二、作用域深度解析
2.1 三种作用域类型
// 1. 全局作用域
var globalVar = "我在任何地方都能访问";
// 2. 函数作用域
function setWidth() {
var width = 100; // 仅在函数内可访问
console.log(width);
}
// 3. 块级作用域(ES6 新增)
if (true) {
let blockVar = "我只在块内存在";
const blockConst = "我也是块级作用域";
}
console.log(blockVar); // ❌ ReferenceError
2.2 作用域嵌套与变量查找
变量查找遵循作用域链规则:从当前作用域向上冒泡查找,直到全局作用域。
var height = 100; // 全局变量
function setWidth() {
var width = 100; // 局部变量
console.log(width, height); // 100, 100
}
setWidth();
console.log(width); // ❌ ReferenceError: width is not defined
三、经典坑位:for 循环中的 setTimeout
这是面试中出现频率最高的问题之一:
// 使用 var(错误示例)
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(`This number is ${i}`); // 打印10个10
}, 1000);
}
// 输出:10 10 10 10 10 10 10 10 10 10
为什么? var 不支持块级作用域,所有的异步回调共享同一个 i,循环结束后 i 变成了 10。
// 使用 let(正确示例)
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(`This number is ${i}`); // 打印0-9
}, 1000);
}
// 输出:0 1 2 3 4 5 6 7 8 9
为什么有效? let 每次循环都会创建一个新的块级作用域,每个回调都绑定着自己那一轮的 i 值。
四、变量提升(Hoisting)
4.1 var 的提升行为
JavaScript 代码在执行前会经过编译阶段,变量声明会被提升到作用域顶部。
console.log(pizza); // undefined(不是报错!)
var pizza = 'Deep Dish';
console.log(pizza); // 'Deep Dish'
// 上面的代码实际执行顺序:
var pizza; // 声明提升
console.log(pizza); // undefined
pizza = 'Deep Dish'; // 赋值留在原地
console.log(pizza); // 'Deep Dish'
4.2 let/const 的临时死区(TDZ)
let 和 const 虽然也会提升,但存在临时死区(Temporal Dead Zone):
console.log(pizza); // ❌ ReferenceError: Cannot access 'pizza' before initialization
let pizza = 'Deep Dish';
在声明之前访问变量会报错,这避免了 var 带来的混乱。
五、最佳实践建议
5.1 优先级原则
- 默认使用 const:除非变量需要重新赋值
- 需要重新赋值时使用 let:如计数器、累加器
- 永远不要使用 var:除非维护遗留代码
// ✅ 好的做法
const API_URL = 'https://api.example.com';
let count = 0;
let isLoading = false;
// ❌ 不好的做法
var oldStyle = '避免使用';
5.2 代码示例对比
// 不好的写法
var winner = false;
winner = '戴'; // 类型随意改变,难以维护
// 好的写法
let winner = false;
winner = true; // 保持布尔类型
// 更好的写法
const WINNER = '戴'; // 如果值不会改变
六、常见错误速查表
| 错误信息 | 含义 | 解决方案 |
|---|---|---|
Assignment to constant variable | 尝试修改 const 声明的变量 | 改用 let,或确认是否需要修改 |
ReferenceError: x is not defined | 变量从未声明 | 先声明再使用 |
Cannot access 'x' before initialization | 在声明前访问 let/const 变量 | 将变量声明移到使用之前 |
结语
ES6 的 let 和 const 不仅解决了 var 的历史遗留问题,还让 JavaScript 的作用域规则更加清晰、可预测。掌握这些基础知识,是写出健壮、可维护代码的第一步。
记住:默认用 const,需要改的用 let,忘记 var。