美文网首页
关于JS函数作用域以及变量提升问题。

关于JS函数作用域以及变量提升问题。

作者: D_R_M | 来源:发表于2019-01-01 17:37 被阅读10次

    前段时间在思否提了一个问题

    function bs(){
        console.log(1);
    }
    function fn(){
        bs();
        if(false){
            function bs(){
                console.log(2)
            }
        }
    }
    fn();
    

    自己的思路
    对于这一段代码的我的理解是:定义了全局bs、fn函数,而bs函数可忽视,下面调用了fn函数此时fn为独立的作用域,fn函数里面的bs函数我把bs函数看作为var bs=function(){},而它又放在一个if(false)里面,所以这导致它紧紧只是定义了bs变量而已并非赋值函数此时bs变量依然是undefined,上面bs()的调用就出现了bs is not a function

    现在总结一下

    下面引用自@rhinel的回答

    个人认为,解释这个问题就要弄明白 SC(作用域链),VO(变量对象)/ AO(活动对象)。
    题主的第一个问题:由于函数域存在bs变量,因此覆盖了全局域的bs变量,所以不会输出全局域的bs变量。

    function bs(){
      console.log(1)
    }
    function fn(){
      console.log(bs)
    }
    fn()
    
    // ƒ bs(){
    //   console.log(1)
    // }
    

    题主的第二个问题:函数域的时候初始化了VO,所以bs是underfined(此时所有变量都是underfined),但是要执行到if的块级区域才会初始化该块级的AO,因此bs在该块级顶部才是function。

    function fn() {
      console.log(bs)
      if (true) {
        console.log(bs)
        function bs() {
          console.log(2)
        }
      }
    }
    fn()
    
    // undefined
    // ƒ bs() {
    //   console.log(2)
    // }
    

    另外刚刚看到评论中提到严格模式,严格模式下是不允许使用未声明变量的,也就是说不能访问SC中的VO。因此访问的是全局变量。此时访问局部变量会报错,但是到if的块级区域下,能够访问AO,实现变量提升。

    (function(){
      "use strict"
    
      function fn(){
        console.log(bs)
        if (true) {
          function bs(){
            console.log(2)
          }
        }
      }
    
      fn()
    })()
    
    // Uncaught ReferenceError: bs is not defined
    
    (function(){
      "use strict"
    
      function fn(){
        if (true) {
          console.log(bs)
          function bs(){
            console.log(2)
          }
        }
      }
    
      fn()
    })()
    
    // ƒ bs(){
    //   console.log(2)
    // }
    

    2018-11-04 补充

    在其他回答的评论中收到疑问,关于变量提升的问题,题主的疑问核心点是变量提升的背景下的提升情况的问题。

    那要先说明代码背景下的变量提升问题:

    首先JS一直是没有块级作用域的,其次ES6新增的块级作用域只针对let和const(修修补补用三年的感觉),然后没有写或者var都只有默认的<script>作用域和函数作用域,只有这种情况存在变量提升,也就是提升至这两个域各自的顶部。

    问题一:为什么会变量提升?

    由于JS是解释即执行的语言,而且是OOP语言,如果一边执行一边修改变量树(SC作用域链)是个非常低效的,因此JS在进入每一个上下文域之后执行代码之前会初始化该域下所有代码,找到var命名及省略命名、函数命名,并把变量加入变量树,意思是告诉后面的执行语句,你有什么变量可以使用。此时在域顶部访问变量会输出underfined。也就是著名的变量提升。

    Scope = AO&VO + [[Scope]]其中,AO始终在Scope的最前端。

    此时回答题主关于变量提升的背景情况:控制台报错is not function而不是is not defined说明变量确实提升了(忽略了if块,直接提升至函数域顶部)。

    问题二:函数名和变量名同名变量的问题?

    函数是各种语言的一等公民,JS也不例外,因此如果函数和变量同名,变量提升后在作用域顶部访问同名变量会输出函数定义,也就是说:不论是先命名变量还是先命名函数,都会被覆盖为函数定义。

    console.log(a)
    var a
    function a () {}
    // ƒ a () {}
    
    console.log(a)
    function a () {}
    var a
    // ƒ a () {}
    

    至于题主的问题,bs却没有被定义为函数,因此要参考我的答案:此时变量仅为VO而不是AO,具体规定要看ECMAScript规范。

    问题三:let和const和ES6块级作用域?

    let和const会形成阮老师提出的暂时性死区问题,实际表现为:在基本作用域(<script>和函数空间)下,初始化变量树时,如果访问到同名let或者const变量定义,会删除变量树上已经命名的变量并且阻止后续var和函数定义、未写的直接定义变量加入变量树(此时会报错已定义)。

    var a
    function b () {
      console.log(a)
      let a
    }
    
    b()
    
    // Uncaught ReferenceError: a is not defined
    

    而let和const加入变量树的行为,则是在执行到该语句的时候,并且与当前上下文的块结构强相关(新的一级执行上下文内定义该变量),因此后面该块内才能够访问该变量,而一旦执行位置离开当前块结构后(新的一级执行上下文),变量树上该变量也因此被删除,并不影响外部变量定义(父块的SC)。

    var a = 1
    {
      let a = 2
      console.log(a)
    }
    console.log(a)
    
    // 2
    // 
    

    问题四:function的规定和行为的差异?

    ES5规定,不允许在块级区域定义函数;ES6规定,可以在块级区域定义函数,但是不存在变量提升,类似于let(可以理解为新增定义方式)。

    但是!!重点!!

    函数命名定义,浏览器从ES5开始就没有遵守规定,可以在块级内定义,因此ES6里面附录提到:为了向后兼容,不遵守就拉倒吧,就跟var一样吧,你开心就好。

    因此,上面一大堆解释,全部是基于这个情况的。

    详细回答可以参见:https://segmentfault.com/q/1010000016823953

    相关文章

      网友评论

          本文标题:关于JS函数作用域以及变量提升问题。

          本文链接:https://www.haomeiwen.com/subject/dhonlqtx.html