JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。
这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting),准确的说是变量声明提升。

先来看一下什么是声明:
声明宣称一个名字的存在,定义则为这个名字分配存储空间,而初始化则是为名字分配的存储空间赋初值。

1
2
var v;  // 声明变量v
v = "hello"; // (定义并)初始化变量v

举个栗子:

1
2
3
4
5
var v = "hello";
(function(){
console.log(v);
var v = "world";
})();

这段代码运行的结果是什么呢?
答案是:undefined

JavaScript是没有块级作用域的。函数是JavaScript中唯一拥有自身作用域的结构。
在function作用域内,变量v的声明被提升了。所以这段代码相当于:

1
2
3
4
5
6
var v = "hello";
(function()
var v; // declaration hoisting
console.log(v);
v = "world";
})();

请注意,变量提升只对var命令声明的变量有效,如果一个变量不是用var命令声明的,就不会发生变量提升。

1
2
console.log(b);
b = 1;

上面的语句将会报错,提示“ReferenceError: b is not defined”,即变量b未声明,这是因为b不是用var命令声明的,JavaScript引擎不会将其提升,而只是视为对顶层对象(如windows对象)的b属性的赋值。

声明提升

当前作用域内的声明都会提升到作用域的最前面,包括变量和函数的声明

1
2
3
4
5
6
(function(){
var a = "1";
var f = function(){};
var b = "2";
var c = "3";
})();

变量a,f,b,c的声明会被提升到函数作用域的最前面,类似如下:

1
2
3
4
5
6
7
(function(){
var a,f,b,c;
a = "1";
f = function(){};
b = "2";
c = "3";
})();

请注意函数表达式并没有被提升,这也是函数表达式与函数声明的区别。进一步看二者的区别:

1
2
3
4
5
6
7
(function(){
// var f1,function f2(){}; // hoisting,被隐式提升的声明
f1(); // ReferenceError: f1 is not defined
f2();
var f1 = function(){};
function f2(){}
})();

上面代码中函数声明f2被提升,所以在前面调用f2是没问题的。虽然变量f1也被提升,但f1提升后的值为undefined,其真正的初始值是在执行到函数表达式处被赋予的。所以只有声明是被提升的。