一、基本概念与声明方式
1. let 声明
let
允许你声明一个块级作用域的局部变量,可以选择性地将其初始化为一个值。
javascript
let x = 1;
if (true) {
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
2. const 声明
const
声明创建一个块级作用域的常量,其值不能被重新赋值。
javascript
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable
const obj = { name: 'John' };
obj.name = 'Jane'; // 合法,修改对象属性
// obj = {}; // 非法,不能重新赋值
二、关键特性详解
1. 块级作用域 (Block Scope)
let
和 const
引入了真正的块级作用域,不同于 var
的函数作用域。
javascript
{
let blockScoped = 'visible inside block';
const alsoBlockScoped = 'same here';
}
console.log(blockScoped); // ReferenceError
console.log(alsoBlockScoped); // ReferenceError
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 0, 1, 2
}
2. 暂时性死区 (Temporal Dead Zone, TDZ)
在声明前访问 let
或 const
变量会导致 ReferenceError
,从进入作用域到声明完成之间的区域称为TDZ。
javascript
console.log(aLetVar); // ReferenceError
let aLetVar = 10;
// 对比var
console.log(aVar); // undefined
var aVar = 10;
3. 无作用域提升 (No Hoisting)
虽然 let
和 const
声明在编译阶段被处理(类似"提升"),但在声明前不可访问。
javascript
function test() {
console.log(hoistedVar); // undefined
console.log(notHoistedLet); // ReferenceError
var hoistedVar = 1;
let notHoistedLet = 2;
}
4. 禁止重复声明
在同一作用域内,let
和 const
不允许重复声明变量。
javascript
let x = 1;
let x = 2; // SyntaxError: Identifier 'x' has already been declared
var y = 1;
let y = 2; // 同样报错
三、与 var 的详细对比
特性 | var | let | const |
---|---|---|---|
作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
提升 | 声明提升,初始化为undefined | 存在TDZ,不可提前访问 | 存在TDZ,不可提前访问 |
全局声明时成为window属性 | 是 | 否 | 否 |
重复声明 | 允许 | 不允许 | 不允许 |
值可变性 | 可变 | 可变 | 不可重新赋值(对象内容可修改) |
四、词法作用域 (Lexical Scope)
let
和 const
遵循词法作用域规则,作用域在代码编写时就已经确定。
javascript
function outer() {
let outerVar = 'outer';
function inner() {
console.log(outerVar); // 可以访问外部变量
let innerVar = 'inner';
}
inner();
// console.log(innerVar); // ReferenceError
}
outer();
五、实际应用场景
1. 循环中的变量绑定
javascript
// var 的问题
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0); // 5,5,5,5,5
}
// let 的解决方案
for (let j = 0; j < 5; j++) {
setTimeout(() => console.log(j), 0); // 0,1,2,3,4
}
2. 常量声明
javascript
// 配置常量
const API_ENDPOINT = 'https://api.example.com';
const MAX_RETRIES = 3;
const DEFAULT_TIMEOUT = 5000;
// 对象常量(内容可修改)
const USER_ROLES = {
ADMIN: 'admin',
USER: 'user',
GUEST: 'guest'
};
3. 块级作用域实用案例
javascript
// 条件性变量声明
if (userLoggedIn) {
let authToken = getToken();
// 仅在此块中使用token
}
// switch语句中的块作用域
switch (condition) {
case 1: {
let message = 'Case 1';
console.log(message);
break;
}
case 2: {
let message = 'Case 2'; // 不冲突
console.log(message);
break;
}
}
六、常见误区与最佳实践
1. const 并不意味着不可变
javascript
const arr = [1, 2, 3];
arr.push(4); // 合法
// arr = [1,2,3,4]; // 非法
const obj = { name: 'John' };
obj.name = 'Jane'; // 合法
如果需要完全不可变的对象,可以使用 Object.freeze()
:
javascript
const frozenObj = Object.freeze({ name: 'John' });
frozenObj.name = 'Jane'; // 静默失败(严格模式下报错)
2. 优先使用 const,其次是 let
现代 JavaScript 开发推荐:
- 默认使用
const
- 只有当变量需要重新赋值时才使用
let
- 避免使用
var
3. 全局声明的影响
javascript
var globalVar = 1;
let globalLet = 2;
console.log(window.globalVar); // 1
console.log(window.globalLet); // undefined
七、底层原理
1. 变量创建的三阶段
- 声明阶段:在作用域中注册变量
- 初始化阶段:分配内存并绑定作用域(TDZ结束)
- 赋值阶段:给变量赋值
var
在声明阶段即初始化(为undefined),而 let
/const
将初始化和赋值分开。
2. 执行上下文的变化
javascript
{
// let x 的声明被"提升"但未初始化
console.log(x); // ReferenceError
let x = 10;
// 初始化完成
}
八、迁移建议
从 var
迁移到 let
/const
时:
- 将函数内所有
var
改为const
- 将需要重新赋值的变量改为
let
- 注意检查循环和闭包中的变量引用
- 使用 linter 工具帮助检测
var
的使用
javascript
// 迁移前
function oldWay() {
var a = 1;
var b = 2;
for (var i = 0; i < 10; i++) {
// ...
}
}
// 迁移后
function newWay() {
const a = 1;
let b = 2;
for (let i = 0; i < 10; i++) {
// ...
}
}