深入理解Javascript: 声明提升

在JS程序中,我们都会需要声明变量和函数,变量声明和函数声明有些什么要注意的呢?

今天就让我们一探究竟。

先看一个例子:

var scope = 'global';
function checkscope(){
    return scope;
    var scope = 'local';
}
var result = checkscope();
console.log(result);

上面例子最后打印出来的result是什么呢,可能有人会说是global,有人会说是local,实际结果却是undifined;

最开始我真是被打击到了,打击过后还是要搞明白啊。

1.作用域

还是先看一个上面例子的变种:

var scope = 'global';
function checkscope(){
    var scope = 'local';
    return scope;
}
var result = checkscope();
console.log(result);

这个可能大家都能说出来是local,是的没错,跟大多数编程语言一样,JS也分全局作用域和函数作用域。

我们先来了解作用域这个概念,在JS中一个变量的作用域是程序源代码中定义这个变量的区域。

全局变量用于全局作用域,在JS代码中的任何地方都是有定义的。然而在函数内的变量只在函数体内有定义。他们是局部变量,作用域是局部的。函数参数也是局部变量,它们只在函数体内有定义。

并且,在函数体内,局部变量的优先级高于同名的全局变量。如果在函数体内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量覆盖。

上面例子的结果就呼之欲出了。

还有一个坑,当我们声明变量时,如果没有加var语句,那么这个变量将会提升为全局变量,如:

var scope = 'global';
function checkscope(){
    scope = 'local';
    return scope;
}
var result = checkscope();
console.log(result); // local

结果变成了local,所以平时声明变量都要加var,不然会造成很多bug,或者原本意义的偏离。

2.变量声明提升

再来看文章开始的例子,为什么会有undefined的结果呢;

原来JS没有块级作用域,JS有函数作用域,变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的,即函数体内的声明的所有变量在函数体内始终是可见的。

有意思的是,这意味着在声明之前甚至可用;JS这个特性被称为变量声明提前,即JS函数内声明的所有变量(不涉及赋值)都被提前至函数体的顶部;这步操作是JS引擎在预编译进行的,是在代码运行之前。

这个例子等价于下面的代码:

var scope = 'global';
function checkscope(){
    var scope;
    return scope;
    scope = 'local';
}
var result = checkscope();
console.log(result);

再来看这个例子,我们就知道由于函数作用域的特性,局部变量在整个函数体始终是有定义的,也就是说,在函数体内局部变量遮盖了同名全局变量。尽管如此,只有在程序执行到var scope = ‘local’时,局部变量才被真正赋值。

所以由于JS没有块级作用域,我们编写程序可以将变量声明放在函数体顶部,这样做法是一个很好的反应真实的变量作用域,减少bug。

3.函数声明提升

老规矩,再来看一个例子:

var foo = 1;
function bar() {
    return foo;
    foo = 2;
    function foo() {return 3;}
    var foo = 4;
}
console.log(bar());

大家会不会说是undefined呢?不是的,这个结果是[function: foo],为什么会出现这种情况呢?

原来函数声明与通过var声明变量一样,函数声明语句中的函数被显式地提前到脚本或函数的顶部,因此它们在整个脚本和函数内部都是可见的。

与变量声明提前不同的是:使用var的话,只有变量声明提前,变量的初始化代码仍然在原来的位置。然而用函数声明语句的话,函数名称和函数体均提前,脚本中的所有函数和函数中所嵌套的函数都会在当前上下文中其它代码之前声明。

为什么要指定是函数声明之前呢,因为使用函数定义表达式创建的函数是不会提前的,见下面的例子:

var foo = 1;
function bar() {
    return foo;
    foo = 2;
    var foo = function foo() {return 3;}
    var foo = 4;
}
console.log(bar()); // undefined

关于函数定义表达式和函数生命表达式的详细内容,以后在深入理解函数中再讲。