块级作用域
ES6 之前创建"块级作用域"
在没有出现 let const 之前,可以利用函数来实现“块级作用域”,防止某些变量暴露到全局去:
- 声明函数创建块级作用域
var a = 23;
function createBlock(){
var a = 24;
console.log(a); // 24
}
console.log(a); // 23
- 立即执行函数创建块级作用域
var a = 23;
(function createBlock(){
var a = 24;
console.log(a); // 24
})()
console.log(a) // 23
ES6 创建块级作用域
1. let 声明
var a = 23;
{
let a = 24;
console.log(a); // 24
}
console.log(a); // 23
从上面的例子可以看到,let 把变量 a 绑定到了 { … } 。var a 的值没有被 let a 覆盖,可以看出两个 a 分别处于两个不同的作用域。此时的 { … } 是一个块级作用域。
- 暂时性死区
console.log(a); // undefined
console.log(b); // ReferenceError
var a;
let b;
提前访问 b 会报一个 “无法在初始化之前访问 b” 的错误。在 b 声明之前是不能访问 b 的,只能在它声明之后访问。
- 提前访问 b 和访问未声明的变量,typeof 的结果是不同的:
{
if(typeof a == 'undefined') console.log('a未声明,访问为undefined');
if(typeof b == 'undefined') console.log('执行吗?'); // ReferenceError
let b;
}
- let 和 for 循环组合
初学 JavaScript 的时候我写过类似下面这样的代码。为了保存 for 循环 i 的值,并对 i 进行操作,当时没有了解作用域的知识,所以结果并没有按照我想的那样进行。上面这段代码中 console.log(i) 只是对 i 进行了引用,没有赋值,并且所有 i 指向的都是同一个作用域下的 i,所以最后打印的结果都是循环结束后的值 i 。
var a = [];
for(var i = 0; i < 5; i++){
a[i] = function consoleNum(){
return console.log(i);
}
}
a[0](); // 5
a[1](); // 5
a[2](); // 5
- 可以用立即执行函数创建块级作用域并把当时的 i 值传进去并保存在块级作用域解决。但有了 let 之后代码可以更加清晰,看如下代码:
var a = [];
for(let i = 0; i < 5; i ++){
a[i] = function consoleNum(){
return console.log(i);
}
}
a[0](); // 0
a[1](); // 1
a[2](); // 2
console.log(i) 中的 i 指向的都是 let 绑定的不同的作用域,结果能达到预期,比立即执行函数简洁。
2. const 声明
- const 声明和 let 一样可以创建块级作用域变量,只是 const 创建的变量的值不能修改。任何尝试修改都会报错。并且在作用域外是访问不到的。
if(true){
var a = 24;
const b = 2019;
a = 25; // 正常
b = 2020; // 报错
}
- const 是对赋值的变量限制不可更改,而不是变量的值被限制。如果 const 的值是对象或数组等复杂数据类型,值仍然是可以修改的。
const a = [1,2,3];
a.push(4); // [1,2,3,4]
a = 24; // TypeError
3. 块作用域函数
ES 6 有了在块内声明的函数,它的作用域在这个块以内。
{
fn(); // '24'
function fn(){
console.log('24');
}
}
fn(); // ReferenceError
从上面这个例子可以看出两点:
- fn 函数只在块级作用域内有效。
- fn 是有提升的,和 let 不一样,没有 let 的暂时性死区。