- let 命令
- let 与 var
- 二者区别:
- 作用域不同:
- 变量提升(Hoisting):
- 临时性死区
- 重复声明:
- 联系:
- 举例说明:
- 二者区别:
- 块级作用域
- 块级作用域的关键字
- 使用 var(无块级作用域)
- 使用 let(块级作用域)
- 使用 const(块级作用域且常量)
- 块级作用域与函数声明
- let 与 var
1. let 命令
ES6 新增了 let 命令,用来声明变量。它的用法类似于 var ,但是所
声明的变量,只在 let 命令所在的代码块内有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
上面代码在代码块之中,分别用 let 和 var 声明了两个变量。然后在
代码块之外调用这两个变量,结果 let 声明的变量报错, var 声明的
变量返回了正确的值。
这表明, let 声明的变量只在它所在的代码块有效。
1.1. let 与 var
let
和 var
都是用来在 JavaScript 中声明变量的关键字,但它们之间存在一些关键区别:
1.1.1. 二者区别:
1.1.1.1. 作用域不同:
var
声明的变量具有函数作用域
或全局作用域
。
如果在函数内部声明,它只在该函数内部可见;如果在函数外部声明,则在整个脚本中都可见,成为全局变量(在浏览器环境下,全局变量会成为 window
对象的属性)。
let
声明的变量具有块级作用域
。
这意味着变量只在它声明的那个代码块(例如,if 语句、for 循环或者一对大括号 {}
内)内有效。
1.1.1.2. 变量提升(Hoisting):
-
var
声明的变量会存在变量提升现象,即使声明在代码执行之后,变量也会被提升至作用域顶部,但在赋值前其值为undefined
。 -
let
不会发生变量提升,如果在声明前访问let
变量,会导致引用错误(ReferenceError)。
1.1.1.3. 临时性死区
当使用 let
或 const
声明变量时,在声明之前访问这些变量会触发错误,这个区域被称为临时死区。
这是为了防止变量在声明前的不确定状态,提高代码的可预测性。
if (true) {
console.log(temp); // 报错,temp在TDZ中
let temp = "ES6";
}
只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)
这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量 tmp ,但是块级作用域内 let 又声明了一
个局部变量 tmp ,导致后者绑定这个块级作用域,所以在 let 声明变
量前,对 tmp 赋值会报错。
ES6明确规定,如果区块中存在 let 和 const 命令,这个区块对这些
命令声明的变量,从一开始就形成了封闭作用域
。凡是在声明之前就使
用这些变量,就会报错。
总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用
的。这在语法上,称为“暂时性死区”(temporal dead zone,简称
TDZ)。
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
上面代码中,在 let 命令声明变量 tmp 之前,都属于变量 tmp 的“死
区”。
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
。
1.1.1.4. 重复声明:
-
使用
var
可以在同一作用域内重复声明同一个变量,后面的声明会覆盖前面的声明。 -
let
不允许在同一作用域内重复声明同一个变量,尝试这样做会导致语法错误。
1.1.2. 联系:
- 它们都是用于变量声明的关键词。
- 在基本的变量赋值和访问操作上,两者的行为是类似的,一旦声明并赋值后,都可以用来存储数据。
1.1.3. 举例说明:
// var 示例
function varExample() {
var x = 1;
if (true) {
var x = 2; // 这里改变了外层函数的x
console.log(x); // 输出2
}
console.log(x); // 输出2,因为var变量提升了且被覆盖
}
// let 示例
function letExample() {
let y = 1;
if (true) {
let y = 2; // 这里创建了一个新的块级作用域变量y
console.log(y); // 输出2
}
console.log(y); // 输出1,因为let有块级作用域
}
varExample(); // 调用函数演示var行为
letExample(); // 调用函数演示let行为
在这个例子中,varExample
函数展示了使用 var
时变量提升以及在相同作用域内重复声明导致的变量覆盖现象。
而 letExample
函数则展示了 let
如何在块级作用域内创建独立的变量,避免了变量覆盖的问题。
ES6 规定暂时性死区和 let 、 const 语句不出现变量提升,主要是为
了减少运行时错误,防止在变量声明前就使用这个变量
,从而导致意料
之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免
此类错误就很容易了。
1.2. 块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域。
ES6(ECMAScript 2015)引入了块级作用域,这是一种新的变量作用域规则,它允许变量在代码块(通常是一对大括号 {}
内)内声明和使用,超出这个块之后,这些变量就会被销毁或不再可见。
这与ES5中的作用域规则不同,ES5中只有全局作用域和函数作用域,没有块级作用域的概念。
实例一
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
实例二
var tmp = new Date();
function f() {
console.log(tmp);
}
f(); // Mon May 06 2024 09:44:26 GMT+0800 (中国标准时间)
实例一代码的原意是, if 代码块的外部使用外层的 tmp 变量,内部使
用内层的 tmp 变量。但是,函数 f 执行后,输出结果为 undefined
原因在于变量提升,导致内层的 tmp 变量覆盖了外层的 tmp 变量。
1.2.1. 块级作用域的关键字
- let: 用于声明一个块级作用域的变量,它可以重复声明,但在同一作用域内不能重复初始化。
- const: 用于声明一个块级作用域的常量,一旦赋值不能改变,也不能重新声明。
1.2.2. 使用 var
(无块级作用域)
在ES5中,即使变量在某个代码块内声明,它也会被提升到包含它的函数作用域或全局作用域中。
var x = 1;
if (true) {
var x = 2; // 这里的x会覆盖外层的x
}
console.log(x); // 输出2
1.2.3. 使用 let
(块级作用域)
使用 let
声明的变量只在声明它的块内有效。
let y = 1;
if (true) {
let y = 2; // 这个y只在这个if块内有效
}
console.log(y); // 输出1,外层的y不受影响
1.2.4. 使用 const
(块级作用域且常量)
const
声明的变量同样遵循块级作用域,而且声明后其值不能被重新赋值。
if (true) {
const z = 3; // z是一个常量,只能在此if块内访问
// z = 4; // 这里会报错,尝试修改常量值
}
// console.log(z); // 这里也会报错,z在块外不可见
1.3. 块级作用域与函数声明
函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
// 情况一
if (true) {
function f() {}
}
// 情况二
try {
function f() {}
} catch(e) {
// ...
}
上面两种函数声明,根据 ES5 的规定都是非法的。
在ES6(ECMAScript 2015)之前,JavaScript只有全局作用域和函数作用域。这意味着在函数外部声明的变量是全局变量,在函数内部声明的变量则是局部变量,仅在该函数内部可见。然而,对于if语句、for循环等代码块内部,并没有自己的作用域。
ES6引入了块级作用域,主要通过let
和const
关键字实现,这使得开发者可以在任何块(例如if语句、for循环的花括号内)内声明变量,这些变量的作用域仅限于该块。
关于函数声明在块级作用域中的行为,ES6规范试图明确函数声明应当具有块级作用域特性,即在块内声明的函数只应在该块内可访问。
理论上,这样的函数声明应该类似于使用let
声明的变量,只在声明它们的块中可见。例如:
{
function sayHello() {
console.log("Hello");
}
sayHello(); // 此处可以调用
}
// sayHello(); // 理论上此处应该报错,因为sayHello只在上面的块内可见
但实际上,由于历史遗留原因和浏览器兼容性问题,早期的ES6实现中,函数声明在块级作用域的行为并不统一。
一些环境(特别是某些浏览器)可能仍然会将块级函数声明提升到包含块的顶部,或者有其他非标准行为。
因此,在实际开发中,为了避免潜在的问题,推荐在块级作用域内使用函数表达式而非函数声明:
{
const sayHello = function() {
console.log("Hello");
};
sayHello(); // 正确使用函数表达式
}
// sayHello(); // 这里依然会报错,因为sayHello是块级作用域内的函数表达式
总结来说,虽然ES6旨在让函数声明遵循块级作用域规则,但由于兼容性问题,最好在块级作用域中使用函数表达式以确保代码的可预测性和兼容性。