JavaScript的作用域和变量提升

作用域

作用域在编程语言中,它是控制着变量与参数的可访问性和生命周期。

作用域这个东西呢,可以假设它是一个采集工人,专注于收集和维护所有声明的标识符(变量),并且将标识符组成一系列的查询,并实现一套规则来管理标识符,确定执行环境(执行上下文)对标识符的使用权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo () {
var a = 1, b = 3;
console.log(c) // 报错,变量c没有定义,父级函数不能获取子函数中的变量
// a => 1, b => 3
function bar () {
var c = 5, d = 7;
a += b + c; // 可访问父级函数的a,b变量,并修改变量a的值
}
// a => 1, b => 3
bar();
// a => 9, b =>
}

foo();

块级作用域

在JS函数中的var声明,其作用域是函数体的全部,在ES6之前并没有块级作用域的说法,在ES5中只有全局作用域和函数作用域。

大多数像C语言的语言都拥有块级作用域,就是在一个代码块中(在一对花括号中的语句)定义的所有变量在代码块的外部是不可访问的,定义在代码块中的变量会在代码块执行完毕后会释放掉。

在ES6中的新特性:let、const提供了块级作用域。

1
2
3
4
if (true) {
let a = 1;
}
console.log(a)// Uncaught ReferenceError: a is not defined

变量提升(Hoisting)

直觉上会认为JavaScript代码在执行时是由上到下一行一行执行的。但实际上并不完全正确,有一种特殊情况会导致这个假设是错误的。

考虑下面的代码

1
2
3
a = 2;
var a;
console.log(a) // a => 2

这种写法固然是不推荐的,但它却正确地输出出来,到底是为什么呢。

再看看一下代码

1
2
console.log(a) // a => undefined
var a = 2

搞明白这个问题,要了解引擎编译器的机制。

引擎在解释JavaSript代码之前首先会对其进行编译,编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。

因此,正确的思路是:在编译的时候,变量和函数在内的所有声明都会在任何代码被执行之前被处理。