下面的程序是什么结果?
复制代码 代码如下:
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
alert(foo);
}
bar();
结果是10;
那么下面这个呢?
复制代码 代码如下:
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
alert(a);
结果是1.
吓你一跳吧?发生了什么事情?这可能是陌生的,危险的,迷惑的,同样事实上也是非常有用和印象深刻的javascript语言特性。对于这种表现行为,我不知道有没有一个标准的称呼,但是我喜欢这个术语:“Hoisting (变量提升)”。这篇文章将对这种机制做一个抛砖引玉式的讲解,但是,首先让我们对javascript的作用域有一些必要的理解。
Javascript的作用域
对于Javascript初学者来说,一个最迷惑的地方就是作用域;事实上,不光是初学者。我就见过一些有经验的javascript程序员,但他们对scope理解不深。javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言,像下面的C程序:
复制代码 代码如下:
#include <stdio.h>
int main() {
int x = 1;
printf("%d, ", x); // 1
if (1) {
int x = 2;
printf("%d, ", x); // 2
}
printf("%d\n", x); // 1
}
输出结果是1 2 1,这是因为C家族的语言有块作用域,当程序控制走进一个块,比如if块,只作用于该块的变量可以被声明,而不会影响块外面的作用域。但是在Javascript里面,这样不行。看看下面的代码:
复制代码 代码如下:
var x = 1;
console.log(x); // 1
if (true) {
var x = 2;
console.log(x); // 2
}
console.log(x); // 2
结果会是1 2 2。因为javascript是函数作用域。这是和c家族语言最大的不同。该程序里面的if并不会创建新的作用域。
对于很多C,c++,java程序员来说,这不是他们期望和欢迎的。幸运的是,基于javascript函数的灵活性,这里有可变通的地方。如果你必须创建临时的作用域,可以像下面这样:
复制代码 代码如下:
function foo() {
var x = 1;
if (x) {
(function () {
var x = 2;
// some other code
}());
}
// x is still 1.
}
这种方法很灵活,可以用在任何你想创建临时的作用域的地方。不光是块内。但是,我强烈推荐你花点时间理解javascript的作用域。它很有用,是我最喜欢的javascript特性之一。如果你理解了作用域,那么变量提升就对你显得更有意义。
变量声明,命名,和提升
在javascript,变量有4种基本方式进入作用域:
•1 语言内置:所有的作用域里都有this和arguments;(译者注:经过测试arguments在全局作用域是不可见的)
•2 形式参数:函数的形式参数会作为函数体作用域的一部分;
•3 函数声明:像这种形式:function foo(){};
•4 变量声明:像这样:var foo;
函数声明和变量声明总是会被解释器悄悄地被“提升”到方法体的最顶部。这个意思是,像下面的代码:
复制代码 代码如下:
function foo() {
bar();
var x = 1;
}
实际上会被解释成:
复制代码 代码如下:
function foo() {
var x;
bar();
x = 1;
}
无论定义该变量的块是否能被执行。下面的两个函数实际上是一回事:
复制代码 代码如下:
function foo() {
if (false) {
var x = 1;
}
return;
var y = 1;
}
function foo() {
var x, y;
if (false) {
x = 1;
}
return;
y = 1;
}
请注意,变量赋值并没有被提升,只是声明被提升了。但是,函数的声明有点不一样,函数体也会一同被提升。但是请注意,函数的声明有两种方式:
复制代码 代码如下:
function test() {
foo(); // TypeError "foo is not a function"
bar(); // "this will run!"
var foo = function () { // 变量指向函数表达式
alert("this won't run!");
}
function bar() { // 函数声明 函数名为bar
alert("this will run!");
}
}
test();
这个例子里面,只有函数式的声明才会连同函数体一起被提升。foo的声明会被提升,但是它指向的函数体只会在执行的时候才被赋值。
上面的东西涵盖了提升的一些基本知识,它们看起来也没有那么迷惑。但是,在一些特殊场景,还是有一定的复杂度的。
变量解析顺序
最需要牢记在心的是变量解析顺序。记得我前面给出的命名进入作用域的4种方式吗?变量解析的顺序就是我列出来的顺序。
复制代码 代码如下:
<script>
function a(){
}
var a;
alert(a);//打印出a的函数体
</script>
<script>