Javascript作用域和变量提升

首先大家看一下下面的代码,判断下会输出什么结果:

var foo = 1;
function bar() {
    if(!foo) {
        var foo = 10;
    }
    alert(foo);
}
bar();

答案是10,
你是否会疑惑条件语句if(!foo)并不会被执行呀,为什么foo会被赋值为10.

再来看看第二个例子:

var a = 1;
function b() {
    a = 10;
    return;
    function a() {}
}
b();
alert(a);

答案是10吗?显然不是,alert输出了1。
如果你仍然对上面两个输出摸不着头脑,那么请认真阅读下面这篇文章。


Javascript作用域

Javascript作用域的问题已经是老生常谈了,但是不一定每一个人都能准确理解。
让我们先来看一下Java中的一个例子:

public int result() {
    int x = 1;
    System.out.println(x);  //1
    if(true) {
        int x = 2;
        System.out.println(x);  //2
    }
    System.out.println(x);  //1
}

程序一次输出了1、2、1,
为什么第3个输出了1而不是2呢?因为在Java中,我们有块级作用域(block-level scope)。在一个代码块中的变量不会覆盖掉代码块外面的变量。我们不妨试一下在Javascript中的表现:

var x = 1;
console.log(x);  //1
if(true) {
    var x = 2;
    console.log(x);  //2
}
console.log(x)  //2

结果if代码块中的变量覆盖了全局变量,那是因为Javascript是一种函数级作用域(function-level scope),所以if中并没有独立维护一个scope,变量x影响到了全局变量x。
C/C++/C#和JAVA都是块级作用域语言,那么在Javascript中,我们怎么实现一种块级作用域的效果呢?答案是闭包:

function foo() {
    var x = 1;
    if(x) {
        (function () {
            var x = 2;
        }());
    }
    x is still 1.

上面代码在if条件块中创建了一个闭包,它是一个立即执行函数,所以相当于我们又创建了一个函数作用域,所以此时内部的x不会对外部产生影响。


Javascript变量提升

在Javascript中,变量进入一个作用域可以通过下面四种方式:

  1. 语言自定义变量:所有的作用域中都存在this和arguments这两个默认变量
  2. 函数形参:函数的形参存在于函数作用域中
  3. 函数声明:function foo() {}
  4. 变量定义: var foo

其中,在代码运行前,函数声明和变量定义通常会被解释器移动到其所在作用域的最顶部,如何理解这句话呢?

function foo() {
    bar();
    var x = 1;
}

上面这段代码,被代码解释器编译完后,将变成下面的形式:

function foo() {
    var x;
    bar();
    x = 1;
}

我们注意到,变量x的定义被移动到函数的最顶部,在bar()后,再对其进行赋值。
再来看一个栗子,下面这两段代码其实是等价的:

function foo() {
    if(false) {
        var x = 1;
    }
    return;
    var y = 1;
}
function foo() {
    var x,y;
    if(false) {
        x = 1;
    }
    return;
    y = 1;
}

所以变量的上升(Hoisting)只是其定义的上升,而变量的赋值并不会上升。

我们都知道,创建一个函数的方法有两种,一种是通过函数声明function foo() {},
另一种是通过定义一个变量 var foo = function() {}
那么这两种在代码执行上有什么区别呢?

来看下面的栗子:

function test() {
    foo();  //Type error, foo is not a function
    bar();  //will alert 'bar'
    var foo = function() {
        alert('foo');
    }
    function bar() {
        alert('bar');
    }
}
test();

在这个例子中,bar能够正常调用,而foo调用时报错了
我们前面说过变量会上升,所以var foo首先会上升到函数体顶部,然而此时的foo尚未定义,为undefined,所以执行会报错。
而对于函数bar,函数本身也是一种变量,所以也存在变量上升的现象,但是它是上升了整个函数,所以bar()才能够顺利执行。

再回到我们一开始提出的两个例子,能理解其输出原理了嘛

同时这也就是为什么,我们写代码的时候,变量定义总要写在最前面。


ES6的变化

在ES6中,存在let关键字,它所声明的变量存在块级作用域。
而且函数本身的作用域,只存在其所在的块级作用域之内,例如:

function f() {console.log('I'm outside!')}
if(true) {
    //重复声明一次函数f
    function f() {console.log('I'm inside!')}
}
f();

上面这段代码在ES5中的输出结果为’I’m inside!’,因为f被条件语句中的f上升覆盖了。
而在ES6中的输出是’I’m outside!’,因为块级中定义的函数不会影响到外部。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章