神奇的函数作用域

Posted 会飞的Tiger

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了神奇的函数作用域相关的知识,希望对你有一定的参考价值。

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

    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

  

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

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

以上是关于神奇的函数作用域的主要内容,如果未能解决你的问题,请参考以下文章

JS 作用域及作用域链

《你不知道的JS(上卷)》作用域闭包

JS作用域作用域链

JavaScript 作用域 与 作用域链

生活大爆炸神奇的angularJS--controller控制器的作用

JS---闭包