块级作用域的概念
根据 w3c
的规定,JavaScript
代码块是这样解释的:
- JavaScript 语句通过代码块的形式进行组合。
- 块由左花括号开始,由右花括号结束。
- 块的作用是使语句序列一起执行。
- JavaScript 函数是将语句组合在块中的典型例子。
ES6
在这个基础上引申出来一个叫做“块级作用域”的概念,即“ {}
中间的部分是一个块级作用域”。例如:for
循环、 if
逻辑判断、while
循环等语句后面的 {}
都是一个块级作用域。
为什么需要块级作用域?
ES5
只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
- 内层变量可能会覆盖外层变量。
- 用来计数的循环变量泄露为全局变量。
- 内层变量覆盖外层变量
var a = 'apple';
function testFn() {
console.log(a); // undefined
if (false) {
var a = 'AAA'; //如果没有这一行,if语句上面的a应该输出为apple
}
}
testFn();
//------------------
//由于ES5没有块级作用域,另外var声明的变量会产生变量提升现象,因此上面的案例等同于下面的代码
var a = 'apple';
function testFn() {
var a;
console.log(a); // undefined
if (false) {
a = 'AAA';
}
}
testFn();
- 循环变量泄露为全局变量
//不存在块级作用域的var语法
for (var i = 0; i < 10; i++) {
//handle
}
console.log(i); //10
//------------
//for循环被看作是块级作用域的let语法
for (let x = 0; x < 10; x++) {
//handle
}
console.log(x); //Error: x is not defined
块级作用域
块级作用域是 ES6
新增的,它对 let
命令声明的变量和 const
命令声明的常量具有较严格的规范。
- 没有内层变量覆盖外层变量的现象
//let命令声明的变量a
let a = 'apple';
function testFn() {
console.log(a); // apple
if (false) {
let a = 'AAA';
}
}
testFn();
//------------
//const命令声明的常量b
const b = 'banana';
function testFn2() {
console.log(b); // banana
if (false) {
const b = 'AAA';
}
}
testFn2();
- 不存在变量泄露为全局变量现象
//块级作用域对var命令无效,a变量泄露到外层作用域了
if (true) {
var a = 'apple';
}
console.log(a); //apple
//let由于块级作用域的作用,不会泄露为全局变量
if (true) {
let b = 'banana';
}
console.log(b); //Error: b is not defined
//const由于块级作用域的作用,也不会泄露为全局变量
if (true) {
const o = 'orange';
}
console.log(o); //o is not defined
- 支持“{}”划分作用域的语法
{
let a = 'apple';
{
let a = 'aaa';
{
let a = 'AAA';
}
}
}
上述案例的每一对“{}”都是一个块级作用域,而且互不干扰。
- ES5 中的作用域
我们知道,ES5 环境中只有全局作用域和函数作用域,那么要想实现类似块级作用域的效果,我们通常可以通过封装函数或封装匿名自调用立即执行函数来实现。例如:
//函数作用域
function testFn() {
var a = 'apple';
}
console.log(a); //Error: a is not defined
//匿名自调用实现类似“块级作用域”的效果
;(function () {
var b = 'banana';
})();
console.log(b); //Error: b is not defined
//es5实现多重“块级作用域”类似效果
;(function () {
var a = 'apple';
;(function () {
var a = 'aaa';
;(function () {
var a = 'AAA';
})();
})();
})();
块级作用域与函数声明
ES5
规定,函数只能在全局作用域和函数作用域之中声明,不能在“块级作用域”中声明。事实上,各大浏览器并没有遵守这个规则。为此,ES6
引入了块级作用域,明确允许在块级作用域之中声明函数。在 ES6
的块级作用域之中,函数声明语句的行为类似于 let
,在块级作用域之外不可引用;但又有别于 let
命令,允许重复声明同名函数且存在函数变量提升。
块级作用域中的函数特征:
- 允许在块级作用域内声明函数。
- 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
- 内层作用域声明的函数不干扰外层作用域的函数。
a. 允许块级作用域内声明函数
if (true) {
function testFn(){
//handle
}
}
b. 同样存在函数变量提升
if (true) {
testFn(); //这里正常输出“hehehe”,表明testFn函数变量产生提升
function testFn() {
console.log('hehehe')
}
}
c. 内层作用域声明的同名函数不干扰外层作用域的同名函数
{
testFn(); //I am outside.
{
testFn(); //I am inside.
function testFn() {
console.log('I am inside.')
}
}
function testFn() {
console.log('I am outside.')
}
testFn(); //I am outside.
}
注意: 由于 ES6
规定“块级作用域内的函数声明类似于 var
声明的变量,默认支持变量提升”,因此,下列案例直接在支持 ES6
的浏览器中运行会报错。
function testFn() {
console.log('outside')
}
(() => {
if (false) {
function testFn() {
console.log('inside')
}
}
testFn(); //Error: testFn is not a function
})();