ES6 let 关键字完整指南
1. 块级作用域
1.1 let vs var 作用域对比
js
复制代码
// var - 函数作用域
function varExample() {
var x = 1;
if (true) {
var x = 2; // 同一个 x
console.log(x); // 2
}
console.log(x); // 2
}
// let - 块级作用域
function letExample() {
let x = 1;
if (true) {
let x = 2; // 不同的 x
console.log(x); // 2
}
console.log(x); // 1
}
1.2 循环中的块级作用域
js
复制代码
// var 在循环中的问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1); // 3, 3, 3
}
// let 在循环中的正确行为
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1); // 0, 1, 2
}
2. 不允许重复声明
2.1 同一作用域重复声明
js
复制代码
// var 允许重复声明
var x = 1;
var x = 2; // 正常工作
// let 不允许重复声明
let y = 1;
let y = 2; // SyntaxError: Identifier 'y' has already been declared
// var 和 let 也不能重复声明
var z = 1;
let z = 2; // SyntaxError: Identifier 'z' has already been declared
2.2 不同作用域的声明
js
复制代码
// 不同块级作用域可以声明同名变量
let x = 1;
if (true) {
let x = 2; // 正常工作
console.log(x); // 2
}
console.log(x); // 1
3. 没有变量提升
3.1 var 的变量提升
js
复制代码
console.log(x); // undefined
var x = 1;
// 等同于
var x;
console.log(x);
x = 1;
3.2 let 的非提升特性
js
复制代码
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 1;
// 函数中也是一样
function example() {
console.log(x); // ReferenceError
let x = 1;
}
4. 暂时性死区(TDZ)
4.1 基本概念
js
复制代码
// 在声明之前使用变量会导致 TDZ 错误
{
console.log(x); // ReferenceError
let x = 1;
}
4.2 复杂场景中的 TDZ
js
复制代码
// 函数参数中的 TDZ
function example(x = y, y = 2) {
return [x, y];
}
example(); // ReferenceError: y is not defined
// 条件语句中的 TDZ
if (true) {
console.log(x); // ReferenceError
let x = 1;
}
5. 不与顶层对象挂钩
5.1 var 与顶层对象的关系
js
复制代码
// 浏览器环境
var x = 1;
console.log(window.x); // 1
// Node.js 环境
var y = 1;
console.log(global.y); // 1
5.2 let 与顶层对象的隔离
js
复制代码
// 浏览器环境
let x = 1;
console.log(window.x); // undefined
// Node.js 环境
let y = 1;
console.log(global.y); // undefined
6. 实际应用场景
6.1 循环中的闭包
js
复制代码
// 使用 var 的问题
const buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log('Button ' + i + ' clicked'); // 总是显示最后一个 i
});
}
// 使用 let 的解决方案
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log('Button ' + i + ' clicked'); // 正确显示当前按钮的索引
});
}
6.2 模块封装
js
复制代码
// 使用 let 创建私有变量
{
let privateData = 'secret';
function doSomething() {
console.log(privateData);
}
// 外部无法访问 privateData
window.api = { doSomething };
}
7. 最佳实践
7.1 推荐用法
js
复制代码
// ✅ 使用 let 声明可变变量
let count = 0;
count++;
// ✅ 在循环中使用 let
for (let i = 0; i < array.length; i++) {
// ...
}
// ✅ 在块级作用域中使用 let
if (condition) {
let temp = calculate();
// ...
}
7.2 避免的模式
js
复制代码
// ❌ 避免在声明前使用变量
function wrong() {
console.log(x); // 避免 TDZ 错误
let x = 1;
}
// ❌ 避免重复声明
let x = 1;
let x = 2; // 语法错误
// ❌ 避免与 var 混用
var y = 1;
let y = 2; // 语法错误
8. 与 const 的对比
8.1 基本区别
js
复制代码
// let 允许重新赋值
let x = 1;
x = 2; // 正常工作
// const 不允许重新赋值
const y = 1;
y = 2; // TypeError: Assignment to constant variable
8.2 对象属性
js
复制代码
// let 和 const 都允许修改对象属性
let obj1 = { a: 1 };
obj1.a = 2; // 正常工作
const obj2 = { a: 1 };
obj2.a = 2; // 正常工作
obj2 = {}; // TypeError: Assignment to constant variable