做用域和做用域鏈

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)();
相關文章
相關標籤/搜索