同学们,早上好! 咱们今天来聊聊 JavaScript 里声明变量的三剑客: var
、let
和 const
。 这三个家伙看似简单,但用起来可是大有门道。 理解它们的差异,能让你的代码更健壮,也避免一些意想不到的 Bug。
咱们今天就从作用域、变量提升 (Hoisting) 和重复声明这三个方面,把它们扒个底朝天!
一、作用域: 领地意识的较量
首先,咱们得知道什么是作用域。 简单来说,作用域就是变量可以被访问的“领地”。 变量只能在它所属的作用域内被访问。 想象一下,你的卧室就是你的领地,你的东西只能在卧室里找到。
-
var
:函数作用域(Function Scope)或者全局作用域(Global Scope)var
是个老家伙了,它的作用域比较宽泛。 如果在函数内部声明var
变量,那么它就是函数作用域,只能在这个函数内部访问。 如果在函数外部声明var
变量,那就是全局作用域,整个脚本都能访问。function myFunction() { var x = 10; if (true) { var y = 20; // 仍然是函数作用域 console.log(x); // 输出 10 } console.log(y); // 输出 20 (y 在函数作用域内) } myFunction(); // console.log(x); // 报错:x is not defined (x 在函数作用域内) // console.log(y); // 报错:y is not defined (y 在函数作用域内) var z = 30; // 全局作用域 function anotherFunction() { console.log(z); // 输出 30 (z 在全局作用域内) } anotherFunction(); console.log(z); // 输出 30 (z 在全局作用域内)
注意
if
语句里面的var y = 20
,虽然在if
里面,但var
的作用域仍然是整个myFunction
函数。 这也是var
容易出错的地方。 -
let
和const
:块级作用域(Block Scope)let
和const
是后起之秀,它们引入了块级作用域的概念。 块级作用域指的是由花括号{}
包围的代码块。let
和const
声明的变量只能在这个代码块内部访问。function myOtherFunction() { let x = 10; if (true) { let y = 20; const z = 30; console.log(x); // 输出 10 console.log(y); // 输出 20 console.log(z); // 输出 30 } // console.log(y); // 报错:y is not defined (y 在块级作用域内) // console.log(z); // 报错:z is not defined (z 在块级作用域内) console.log(x); // 输出 10 } myOtherFunction(); // console.log(x); // 报错:x is not defined (x 在函数作用域内)
可以看到,
y
和z
只能在if
语句块里面访问。 出了这个块,就找不到它们了。 这使得代码更加清晰,也减少了变量污染的可能性。for
循环和while
循环也是块级作用域:for (let i = 0; i < 5; i++) { console.log(i); // 输出 0, 1, 2, 3, 4 } // console.log(i); // 报错:i is not defined while (true) { const flag = true; break; } // console.log(flag); // 报错:flag is not defined
let
和const
的块级作用域让变量的管理更加精细。
二、变量提升 (Hoisting): 悄悄地挪移
变量提升是 JavaScript 一个比较让人困惑的特性。 简单来说,就是在代码执行之前,JavaScript 引擎会把变量声明(注意,仅仅是声明,不包括赋值)提升到当前作用域的顶部。
-
var
:提升并初始化为undefined
var
声明的变量会被提升到作用域顶部,并且会被初始化为undefined
。 这意味着你可以在声明之前使用var
变量,但它的值是undefined
。console.log(myVar); // 输出 undefined var myVar = 10; console.log(myVar); // 输出 10
虽然代码在
var myVar = 10;
之后才声明myVar
,但是由于变量提升,console.log(myVar)
在执行的时候,myVar
已经被声明了,只不过值是undefined
。这很容易导致一些意外的 Bug。
-
let
和const
:提升但未初始化let
和const
声明的变量也会被提升,但是它们不会被初始化。 这意味着如果你在声明之前使用let
或const
变量,会报错:ReferenceError: Cannot access '...' before initialization
。// console.log(myLet); // 报错:ReferenceError: Cannot access 'myLet' before initialization let myLet = 20; console.log(myLet); // 输出 20 // console.log(myConst); // 报错:ReferenceError: Cannot access 'myConst' before initialization const myConst = 30; console.log(myConst); // 输出 30
这种现象被称为“暂时性死区”(Temporal Dead Zone,简称 TDZ)。 变量在 TDZ 中,是无法访问的。
TDZ 的存在,可以帮助我们更早地发现错误,避免一些潜在的问题。
三、重复声明: 谁能忍受花心大萝卜?
重复声明指的是在同一个作用域内,使用相同的名称声明多个变量。
-
var
:允许重复声明var
允许在同一个作用域内重复声明变量。 后面的声明会覆盖前面的声明。var myVar = 10; var myVar = 20; // 覆盖了之前的声明 console.log(myVar); // 输出 20
这很容易导致代码逻辑混乱,因为你可能不小心覆盖了之前的变量。
-
let
和const
:不允许重复声明let
和const
不允许在同一个作用域内重复声明变量。 如果尝试这样做,会报错:SyntaxError: Identifier '...' has already been declared
。let myLet = 10; // let myLet = 20; // 报错:SyntaxError: Identifier 'myLet' has already been declared const myConst = 20; // const myConst = 30; // 报错:SyntaxError: Identifier 'myConst' has already been declared
这可以避免意外的变量覆盖,让代码更加健壮。
四、const
的特殊性: 承诺一旦许下,永不改变
const
除了拥有 let
的所有特性之外,还有一个特殊的特性:它声明的是常量,一旦赋值,就不能再修改。
const myConst = 40;
// myConst = 50; // 报错:TypeError: Assignment to constant variable.
console.log(myConst); // 输出 40
但是,需要注意的是,const
声明的常量,如果是一个对象或者数组,那么常量存储的是对象或者数组的引用。 你可以修改对象或者数组的内容,但是不能修改常量指向的引用。
const myObject = {
name: "Alice",
age: 30
};
myObject.age = 31; // 可以修改对象的内容
console.log(myObject); // 输出 { name: "Alice", age: 31 }
// myObject = { name: "Bob", age: 25 }; // 报错:TypeError: Assignment to constant variable.
同样,对于数组:
const myArray = [1, 2, 3];
myArray.push(4); // 可以修改数组的内容
console.log(myArray); // 输出 [1, 2, 3, 4]
// myArray = [4, 5, 6]; // 报错:TypeError: Assignment to constant variable.
所以,const
并不是说变量的值完全不能变,而是说变量指向的内存地址不能变。
五、总结: 三剑客的对比
为了方便大家记忆,我们用一个表格来总结一下 var
、let
和 const
的区别:
特性 | var |
let |
const |
---|---|---|---|
作用域 | 函数作用域/全局作用域 | 块级作用域 | 块级作用域 |
变量提升 | 提升并初始化为 undefined |
提升但未初始化(TDZ) | 提升但未初始化(TDZ) |
重复声明 | 允许 | 不允许 | 不允许 |
修改变量值 | 允许 | 允许 | 不允许 (对于基本类型), 对于对象和数组可以修改内容,但不能修改引用 |
六、最佳实践: 如何选择?
那么,在实际开发中,我们应该如何选择使用哪个关键字呢?
-
优先使用
const
: 如果变量的值在初始化之后不会再改变,那么应该使用const
。 这可以明确地告诉其他开发者,这个变量是一个常量,不要修改它。 这样做可以提高代码的可读性和可维护性。 -
如果变量的值需要改变,使用
let
: 如果变量的值在初始化之后需要改变,那么应该使用let
。 -
尽量避免使用
var
:var
的作用域比较宽泛,容易导致一些意想不到的 Bug。 在现代 JavaScript 开发中,应该尽量避免使用var
。
举个例子:
// 循环计数器,值会改变,使用 let
for (let i = 0; i < 10; i++) {
console.log(i);
}
// 常量,不会改变,使用 const
const PI = 3.14159;
// 需要改变的对象,使用 let
let user = {
name: "Alice",
age: 30
};
user.age = 31;
console.log(user);
// 函数内部需要使用的变量,使用 let 或 const
function calculateArea(radius) {
const area = PI * radius * radius; // area 的值不会改变,使用 const
return area;
}
console.log(calculateArea(5));
总而言之,const
和 let
提供了更严格的作用域规则,可以帮助我们编写更清晰、更健壮的代码。 尽量避免使用 var
,拥抱 const
和 let
吧!
好了,今天的讲座就到这里。 希望大家能够掌握 var
、let
和 const
的区别,并在实际开发中灵活运用。 下课!