1. for循环作用域
我们都知道,一个for循环由循环头和循环体两个部分组成。
在JavaScript中,这两个组成部分的作用域是分开的,循环头部分是一个父作用域,循环体部分是一个单独的子作用域。
因此,我们在循环头中用let关键字声明的变量,在循环体中是可以再次声明,不会报错的。
for (let i = 0; i < 3; i++) {
let i = 'test';
console.log(i);
}
// test
// test
// test
2. for循环头中的变量声明
在学习js的过程中,经常能看到类似这样的代码:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
因为循环头中的i是用var声明的,是一个全局变量,每个a[i]中console.log(i)都指向全局变量i,因此输出为10
如果将var关键字改为let
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
从这个例子中,我们可以看出来,每一轮循环中的i都是重新声明的,因此每个a[i]中才可以输出相对应的数字。
因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量
i时,就在上一轮循环的基础上进行计算。
循环头中给i赋值为0的操作只会进行一次。
3. let、const的暂时性死区TDZ(Template Dead Zone)和变量提升
3.1 TDZ
ES6 明确规定,如果区块中存在
let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
3.2 变量提升
在搜索关于变量提升问题答案的过程中,本人发现了以下两个链接:
- 1.知乎🔗
- 2.stackoverflow🔗
看到关于变量提升的高赞答案:
x = y = "global";
// function scope:
(function() {
x; // Uncaught ReferenceError: Cannot access 'x' before initialization
y; // global
let/… x;
var y
}());
// block scope (not for `var`s):
{
x; // Uncaught ReferenceError: Cannot access 'x' before initialization
let/const/… x;
}
以上代码中,我们先声明了一个全局变量x,这时不论我们在哪种作用域中,都能正常输出global。
但是如果我们使用let或const再次声明变量,就会出现报错。这说明我们使用let/const声明变量时仍存在某种提升(当然这个情况可以用暂时性死区的概念来解释)
3.2.1 变量生命周期
以上关于变量提升的阐述都不够清晰,变量生命周期的概念有助于更清晰的理解变量提升。 变量生命周期包括以下三个阶段:
- 声明阶段(
Declaration phase) 在作用域内注册一个变量 - 初始化阶段(
Initialization phase) 分配内存并将之与作用域中的对象进行绑定。在此步骤中,变量将使用进行自动初始化undefined。 - 分配阶段(
Assignment phase) 为完成初始化的变量分配一个值。
var和let/const最大的区别在于,var变量经过声明阶段后就在作用域顶部立即通过初始化阶段;而let/const在声明阶段和初始化阶段之间有暂时性死区,直到执行了声明语句,才完成初始化阶段。
The variable passes the declaration phase and right away the initialization phase at the beginning of the scope