JavaScript let 变量声明指南:告别 var 的阴影
ES6(ECMAScript 2015)的到来给 JavaScript 带来了很多重要的新特性,中 let 和 const 的引入可以说是一次变量声明方式的革命。在 let 出现之前,开发者只能使用 var,忍受着它那些令人头疼的特性。let 的出现,让我们终于有了一个更合理、更符合直觉的选择。
let 的语法和 var 非常相似,只是把关键字换了一下:
// 代码号:https://www.ebingou.cn/
let username; // 只声明,不赋值,此时值是 undefined
let age = 25; // 声明并赋值
let site = "代码号"; // 字符串类型
let isLearning = true; // 布尔类型
看起来和 var 没什么区别对吧?但真正重要的是它们背后的行为差异。下面我们通过一系列场景,深入理解 let 的特性。
作用域:终于有了块级作用域
let 最核心的特性就是引入了块级作用域。什么意思呢?就是用 {} 花括号包裹起来的代码块,比如 if、for、while 或者单纯的 {},都会形成自己的作用域。在这个块里面用 let 声明的变量,外面访问不到。
全局作用域示例:
// 教程:https://www.ebingou.cn/jiaocheng/
let globalVar = "我在全局作用域";
function test() {
console.log("函数内部访问:" + globalVar); // 可以访问
}
test();
console.log("函数外部访问:" + globalVar); // 也可以访问
// 输出正常,全局变量到处都能用
函数作用域示例:
// 编程:https://www.ebingou.cn/biancheng/
function showMessage() {
let message = "函数内部的秘密";
console.log("函数内:" + message); // 输出:函数内:函数内部的秘密
}
showMessage();
console.log("函数外:" + message);
// 报错:ReferenceError: message is not defined
可以看到,函数内部用 let 声明的变量,到了外面就找不到了,这才是符合常理的行为。
块级作用域示例——这才是重点:
// 源码:https://www.ebingou.cn/yuanma/
{
let blockVar = "我活在花括号里";
console.log("块内部:" + blockVar); // 输出正常
}
console.log("块外部:" + blockVar);
// 报错:ReferenceError: blockVar is not defined
if (true) {
let ifVar = "if 块里的变量";
console.log(ifVar); // 输出正常
}
// console.log(ifVar); // 报错,访问不到
for (let i = 0; i < 3; i++) {
// 循环体
}
// console.log(i); // 报错,i 只在循环内有效
个人经验分享:这才是我们真正需要的变量作用域!每个块都是独立的沙箱,变量不会到处泄露。写代码的时候可以更放心,不用担心某个临时变量污染了全局或者影响他地方。
暂时性死区:提前访问?不行
let 也有提升,但它的提升方式和 var 不同。用 let 声明的变量会被提升,但是在初始化之前访问它会报错,而不是得到 undefined。这段从进入作用域到变量声明之间的区域,被称为暂时性死区。
console.log(myLet);
// 报错:ReferenceError: Cannot access 'myLet' before initialization
let myLet = "代码号学习编程";
个人见解:我觉得这个设计非常合理。既然变量还没初始化,就不应该让你用,直接报错可以尽早发现错误。相比之下,var 那种返回 undefined 的静默失败,反而容易掩盖问题。
重复声明:不允许!
在同一个作用域内,用 let 重复声明同一个变量是不允许的,会直接报错。
let course = "JavaScript";
let course = "Python";
// 报错:SyntaxError: Identifier 'course' has already been declared
// 但是在不同作用域里可以重复声明
let name = "Alan";
{
let name = "Bob"; // 这里没问题,是块内的新变量
console.log("块内:" + name); // 输出:块内:Bob
}
console.log("块外:" + name); // 输出:块外:Alan
这个限制能有效防止意外覆盖变量。如果你不小心重复声明了,JavaScript 引擎会立刻提醒你,而不是像 var 那样静默覆盖。
与 var 的详细对比
为了更清晰地理解 let 和 var 的区别,我们来看几个对比场景:
场景一:循环中的表现
// 使用 var
for (var j = 0; j < 3; j++) {
setTimeout(() => {
console.log("var:", j);
}, 100);
}
// 输出:3 3 3
// 使用 let
for (let k = 0; k < 3; k++) {
setTimeout(() => {
console.log("let:", k);
}, 100);
}
// 输出:0 1 2
主观建议:这个例子充分说明了为什么在循环中要用 let。let 为每次迭代都创建了一个新的绑定,所以异步回调能拿到正确的值。用 var 的话,所有回调共享同一个变量,结果就乱了。
场景二:全局对象的属性
var varGlobal = "我是 var";
let letGlobal = "我是 let";
console.log(window.varGlobal); // 输出:我是 var
console.log(window.letGlobal); // 输出:undefined
用 let 声明的全局变量不会成为 window 对象的属性,这样可以避免意外覆盖全局对象上原有的属性,减少命名冲突。
何时使用 let
let 适用于绝大多数需要变量的场景。当你需要一个值可以改变的变量时,let 是优选。
// 计数器
let count = 0;
count++; // 修改值
// 循环变量
for (let i = 0; i < 10; i++) {
// ...
}
// 条件分支里的临时变量
if (userLoggedIn) {
let greeting = "欢迎 " + username;
showMessage(greeting);
}
// 这里的 greeting 不会泄露到外面
何时不用 let
如果你的变量初始化后就不打算改变,应该用 const 而不是 let。用 const 可以明确表达意图,还能防止意外修改。
// 更好的做法
const SITE_NAME = "代码号";
const MAX_COUNT = 100;
个人习惯:我一般会默认用 const,只有当明确知道这个变量后面需要重新赋值时,才改成 let。这样可以避免很多无心之失。
本节课程知识要点
-
块级作用域:
let声明的变量作用域限定在最近的{}内,不会泄露到外部。 -
暂时性死区:在声明前访问
let变量会抛出ReferenceError,而非得到undefined。 -
禁止重复声明:同一作用域内,
let不允许重复声明同名变量。 -
不绑定全局对象:全局声明的
let变量不会成为window的属性。 -
循环中的独立绑定:
for循环中使用let,每次迭代都会创建新的变量绑定,解决了异步回调中的经典问题。 -
与 var 的核心区别:
var是函数作用域或无作用域,let是块级作用域;var允许重复声明,let不允许;var有提升且可提前访问得undefined,let有提升但存在暂时性死区。
let 的引入让 JavaScript 的变量声明变得科学多了。它保留了 var 的优点,同时修复了 var 那些让人头疼的设计缺陷。在日常开发中,除非你需要维护老代码,否则都应该优先使用 let 和 const。这不仅能让代码更清晰,也能避免很多隐晦的 bug。