let
特性
- 声明的变量只在let所在的代码块内生效
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
- 不存在变量声明提升
// var 的情况 console.log(foo); // 输出undefined var foo = 2; // let 的情况 console.log(bar); // 报错ReferenceError let bar = 2;
- 暂时性死区(即在变量声明前不能赋值)
if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 }
- 在相同作用域内,不允许重复声明
// 报错 function func() { let a = 10; var a = 1; } // 报错 function func() { let a = 10; let a = 1; }
块级作用域的概念
let实际上为 JavaScript 新增了块级作用域。它让一些不合理的场景变的合理
- 场景1 内层变量可能会覆盖外层变量
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。
2.场景2 用来计数的循环变量泄露为全局变量
var s = 'hello'
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
ES6 的块级作用域
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函数有两个代码块,都声明了变量n,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10。
特性
1. 允许块级作用域的任意嵌套
{{{{{let insane = 'Hello World'}}}}};
2. 外层作用域无法读取内层作用域的变量,
{{{{
{let insane = 'Hello World'}
console.log(insane); // 报错
}}}}
3. 内层作用域可以定义外层作用域的同名变量
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}}
块级作用域与函数声明
函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
// 情况一
if (true) {
function f() {}
}
// 情况二
try {
function f() {}
} catch(e) {
// ...
上面两种函数声明,根据 ES5 的规定都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
块级作用域声明函数规则
1. 允许在块级作用域内声明函数。
2. 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
3. 同时,函数声明还会提升到所在的块级作用域的头部。