神奇的函数作用域

前言

今天发现了两个关于函数作用域的神奇例子,这里和大家分享分享:

第一个例子

    var a = 1;
 
    function foo() {
      if (!a) {
        var a = 2;
      }
      alert(a);
    };
 
    foo();

上面这段代码在运行时会产生什么结果?

 我们来分析一下:

        1.创建了全局变量 a,定义其值为 1
        2.创建了函数 foo
        3.在 foo 的函数体内,if 语句将不会执行,因为 !a 会将变量 a 转变成布尔的假值,也就是 false
        4.跳过条件分支,alert 变量 a,最终的结果应该是输出 1 

看起来无懈可击的分析,但是实际上,结果错误。答案竟然是 2!为什么?

什么叫申明?

        是指你声称某样东西的存在,比如一个变量或一个函数;但你没有说明这样东西到底是什么,仅仅是告诉解释器这样东西存在而已;

什么叫定义?

        是指你指明了某样东西的具体实现,比如一个变量的值是多少,一个函数的函数体是什么,确切的表达了这样东西的意义。

所以上面的代码实际上可以写成这样:

    var a;
    a = 1;
 
    function foo() {
      var a;    // 关键在这里
      if (!a) {
        a = 2;
      }
      alert(a);   // 此时的 a 并非函数体外的那个全局变量
    }
    foo();

然后又有人会问,不是有个if吗?if不成立哪就不会为a赋值为2。

因为 JavaScript 没有块级作用域(Block Scoping),只有函数作用域(Function Scoping),所以说不是看见一对花括号 {} 就代表产生了新的作用域,和 C 不一样!

当解析器读到 if 语句的时候,它发现此处有一个变量声明和赋值,于是解析器会将其声明提升至当前作用域的顶部(这是默认行为,并且无法更改),这个行为就叫做 Hoisting。

怎样能够alert出那个a=1?

    let a;
    a = 1;
 
    function foo() {
      let a;    // 关键在这里
      if (!a) {
        a = 2;
      }
      alert(a);   // 此时的 a 并非函数体外的那个全局变量
    }
    foo();

es6的语法,javascript是有块级作用域的。

还可以通过闭包的方式实现:

  var a = 1;
     
    function foo() {
      if (!a) {
        (function() {    
          var a = 2;    
        }());        
      };
      alert(a);
    };
     
    foo();

第二个例子

 var a = 1;        
    function test() {
        foo();
                 
        var foo = function() {
            alert(a);
        }
    }
                 
    test();

这个运行的结果是什么?初略一看,alert(1),但是实际上报错。

  Uncaught TypeError: foo is not a function。

为什么会这样?

  提升的仅仅是变量名 foo,至于它的定义依然停留在原处。因此在执行 foo() 之前,作用域只知道 foo 的命名,不知道它到底是什么,所以执行会报错(通常会是:undefined is not a function)。这叫做函数表达式(Function Expression),函数表达式只有命名会被提升,定义的函数体则不会。

 var a = 1;        
    function test() {
        var foo;
        foo();             // 这个时候函数foo,只声明,未赋值。
        foo = function() {
            alert(a);
        }
    }
                 
    test();

怎么改?   

 var a = 1;        
    function test() {
        var foo = function() {
            alert(a);
        }
        foo();
    }
                 
    test();        // 1

这个例子也展示了函数声明与函数表达式的差别,函数申明会放到作用域的顶部,函数表达式则不会。

最后引用很多书中的一句话:“请始终保持作用域内所有变量的声明放置在作用域的顶部”,相信现在的你对这句话应该有一个认识了。

posted @ 2017-08-12 11:23  会飞的Tiger  阅读(555)  评论(0编辑  收藏  举报