做用域和做用域链

Js 做用域和做用域链javascript

 

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

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

  变量和函数做用域须要区分
数组

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

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

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

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

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

 上述代码中:code

  第一个自执行函数内部,因没有声明关键字,自动把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、全局

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

2、函数

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

  

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

 

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

    首先预编译过程

       1、首先建立VO对象

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

       3、自执行函数造成执行上下文,压入执行栈

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

       5、预编译结束开始执行

    执行过程:

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

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

    预编译:

      1、建立AO对象

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

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

      4、预编译结束开始执行

    执行过程: 

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

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

      3、执行找到的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);

编译过程:

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

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

  三、编译到function a(){console.log('函数声明')},查看AO上有a由于是声明函数因此把function a(){console.log('函数声明')}赋值给AO.a

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

执行过程:

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

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

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

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

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

了解做用域后,咱们知道为何会变量提高 ,咱们也知道 js查找一次变量的消耗,因此咱们尽量地把全局的变量 赋值到的函数体内部使用,还有避免递归内使用全局变量

下面出个例子你们猜一下输出内容:

  

    var _var = 22;
    function wrap(_var){
        console.log(_var)
        var _var = 11;
        return function _var(){
            console.log(_var)
        }
    }
    wrap(1111)();
相关文章
相关标签/搜索