zoukankan      html  css  js  c++  java
  • 作用域和作用域链

    Js 作用域和作用域链

    作用域指的就是 变量和函数 能在哪些区域能调用和使用

      划分区域一般指得都是函数或with、let和const(暂时性死区)、script标签等来划分作用域

      变量和函数作用域需要区分

        全局变量:声明函数的script标签内部和接下来要执行的script标签,声明变量之后调用都能访问到,声明之前调用得到undefined

        局部变量:声明变量之后函数内部或子函数都能访问到,声明之前调用得到undefined

        全局函数:声明函数的script标签内部和接下来要执行的script标签都能调用

        局部函数:在声明函数的环境内或自身内部或 其子函数都能调用

    (function(){
        abc=111;
    })()
    console.log(abc)//111
    (function(){
        console.log(abc)//undefined
        var abc=111;
    })()
    console.log(abc)//ReferenceError
    
    
    
    

     上述代码中:

      第一个自执行函数内部,因没有声明关键字,自动把abc变量挂载全局对象下,所以函数外部才能访问变量abc

      第二个自执行函数内部,输出adc undefined因下面有var 声明关键字,造成变量提升(后面我会讲到变量提升原理),子执行函数外面抛出引用错误,因全局对象下没有abc变量

        var g = 0;
        (function(){
          
          console.log(g)//0
          fnA()//ok
    function fnA(){ var a = 1; console.log(g);//0 console.log(a);//1 console.log(b);//ReferenceError function fnB(){ var b = 2; console.log(g);//0 console.log(a);//1 console.log(b);//2
              fnB()//ok } fnB()//ok
           fnA()//ok } })() console.log(g);
    //0 console.log(a);//ReferenceError console.log(b);//ReferenceError

    上述代码:

       变量b 能在自身声明内部调用,不能再外部调用

       函数fnB 能在自身环境 自身内部 和自身子函数调用  不能再创建自身环境 父级调用(闭包除外)

    前面讲解了什么是作用域,下面说说 作用域链 

    作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。《出自javascript高级程序设计第四章》

    解释上述代码执行过程之前先说一下预编过程

    一、全局

    1. 创建VO变量对象(全局对象 Variable Object);
    2. 查找变量声明和函数表达式,查找到的变量和函数表达式,如果VO对象上没有则创建该属性,如果有不做任何操作,(这个步骤网上很多说是并赋值undefined,我觉得是因为js的属性类型Value默认就是undefined,而不是赋值上去的),如果检测到函数声明,VO对象上有名字相同的属性,赋值函数体,没有则创建并赋值函数体

    二、函数

    1. 创建AO活动对象(Active Object)
    2. 函数的arguments对象内的参数和方法创建到AO上
    3. 查找变量声明和函数表达式,查找到的变量和函数表达式,如果VO对象上没有则创建该属性,如果有不做任何操作,如果检测到函数声明,VO对象上有名字相同的属性,赋值函数体,没有则创建并赋值函数体

      

      《javascript高级程序设计第四章》中解释到:作用域链的前端,始终就是当前执行的代码所在的环境里的AO(活动对象),我把[[scope]]对象比作成数组,数组的第一项就是自身AO对象,第二项就是父函数的AO对象,以此类推直到全局对象VO,如果自身函数活动对象AO没有要查找的变量,就向父级函数对象查找,直到VO对象,如果都没有就会抛出引用错误异常。(这种行为叫回溯)

      用作用域链解释一下上述代码的执行过程和为什么报错:

        首先预编译过程

           一、首先创建VO对象

           二、检测变量声明,查看VO对象有没有属性g,如果没有向VO对象上创建属性g,如果有不做任何操作,赋值压入执行栈

           三、自执行函数形成执行上下文,压入执行栈

           四、剩下的三个console依次压入执行栈

           五、预编译结束开始执行

        执行过程:

          一、var g = 0 当前代码的执行是window,找到window下的属性对象[[scope]](也就是作用域链),,里面保存着VO对象,在VO对象上找到属性g 并赋值0

          二、执行 自执行函数上下文,进行预编译

        预编译:

          一、创建AO对象

            二、arguments对象内的参数和方法挂载到AO上

          三、检测到函数fnA声明,在AO上创建fnA属性并赋值函数fnA的函数体

          四、预编译结束开始执行

        执行过程: 

          一、执行console.log(g)  console就不进去再说了。。直接说输出g  找到当前代码执行环境里的属性对象[[scope]]的自身Ao,找g属性发现没有,回溯查找到VO对象,找到属性g并输出

          二、执行 fnA()   找到当前代码执行环境里的属性对象[[scope]]的自身Ao,找到fnA属性

          三、执行找到的fnA函数体

        依次类推。。。。

    function fn(){
        console.log(a)//undefined
        var a=1111;
    }
    fn();

       预编译进入fn函数 创建Ao对象  声明变量a ,查到自身Ao对象上没有属性a ,在Ao对象上创建属性a

      执行console.log(a) 找到当前代码执行环境里的属性对象[[scope]]的自身Ao,找到a属性 这时候属性还没有赋值 输出默认值undefined,(这地方解释了变量提升)

    再来个例子:

        function wrap(a){
            console.log(a)//f a(){console.log('函数声明')}
            var a = 1;
            function a(){
                console.log('函数声明')
            }
            a();//a is not a function
            a = function(){
                console.log('函数表达式')
            }
            a();//函数表达式
        }
        wrap(11111);

    编译过程:

      1、创建函数对应AO对象,把arguments内的属性和方法添加到AO对象上,

      2、Var a查看AOa属性吗,检测到有a属性(形参上有a属性),不做任何操作

      3、编译到function a(){console.log('函数声明')},查看AO上有a因为是声明函数所以把function a(){console.log('函数声明')}赋值给AO.a

      4、编译到a=function(){console.log('函数表达式')}变量赋值查看到AOa不做任何操作

    执行过程:

      1、console.log(a) ;              找到代码执行环境的Ao对象因对象有a属性,输出函数声明a的函数体

      2、var a = 1;                        找到Ao下的属性a赋值1

      3、function a(){console.log('函数声明')};          函数声明执行阶段不做操作,因为a是number类型1  所以进行函数执行报错、

      4、a=function(){console.log('函数表达式')} ;      找到自身环境Ao下的属性a 赋值 函数体

      5、a();                    找到自身环境Ao下的属性a 执行输出  //函数表达式

    了解作用域后,我们知道为什么会变量提升 ,我们也知道 js查找一次变量的消耗,所以我们尽可能地把全局的变量 赋值到的函数体内部使用,还有避免递归内使用全局变量

    下面出个例子大家猜一下输出内容:

      

        var _var = 22;
        function wrap(_var){
            console.log(_var)
            var _var = 11;
            return function _var(){
                console.log(_var)
            }
        }
        wrap(1111)();
  • 相关阅读:
    vue 使用sass 和less
    生命周期函数以及vue的全局注册
    vue-router的简单实现原理
    vue的三种传参方式
    配置router列表
    vue传参二
    Gym 101933E(状态压缩+记忆化搜索)
    Gym 101933 A(dp)
    Gym 101775H(dp)
    Gym 101775 D (思维)
  • 原文地址:https://www.cnblogs.com/sxldy/p/10933760.html
Copyright © 2011-2022 走看看