詳解JavaScript做用域及做用域鏈

  1、js沒有塊級做用域函數

  在強類型語言中都有塊級做用域,例如,有以下C#代碼spa

for(int i = 0; i < 10; i++)
{
      //do something 
}
Console.WriteLine(i);   

  這樣的代碼是沒法經過編譯的,由於循環變量i是一個局部變量,它的做用域範圍只在for循環的大括號{}以內,出了這個大括號就不是i的做用域了,因此Console.WriteLine(i); 這條語句就不能編譯經過了。3d

  但若是這樣相似的代碼是js寫的就另當別論了。好比:code

for(var i = 0; i < 10; i++){
     //do something;  
}
console.log(i);//輸出10

  這個代碼是沒有問題的,運行結果是10 。這說明了js沒有傳統意義上的塊級做用域。對象

  但這不表明js沒有局部做用域。js的做用域只有全局做用域和函數做用域,js也只有函數能產生局部做用域。blog

  1、全局做用域ip

  只要是在一對<script>標籤以內直接聲明的對象,它的做用域就是全局的。作用域

  若是一個對象是在function內部聲明的,那麼它的做用域就是局部的,函數內部的做用域。it

   好比:io

<script>
    var a = 123; 
    console.log(a); //輸出123
    function f1(){ 
       console.log(a);
    } 
    f1();//輸出123
</script>

  此例中,a就是直接聲明在了一對<script>標籤內的,不是聲明在某一個函數以內的,那麼它是個全局做用域,因此不論是在f1函數內部,仍是在外部,均可以訪問到變量a

  2、函數做用域

  把上邊的例子稍微改改

<script>
    var a = 123; 
    console.log(a); //輸出123
    function f1(){ 
       var num = 10;
       console.log(a);
    } 
    f1();//輸出123
    console.log(num); // 報錯,沒法訪問到num
</script>

 

  這個代碼會報錯,由於num是在f1函數內部聲明的,它的做用域僅限於f1函數聲明的內部,也就是f1那個大括號{}內部,因此在函數聲明的外部訪問num確定是不能夠的。

  咱們可能會看到一個詞,說js中的做用域是:詞法做用域。那麼怎麼理解這個所謂的「詞法做用域」呢?

  看這個例子

<script>
    var a = 123; 
    console.log(a); //輸出123
    function f1(){ 
       console.log(a);
    } 
    function f2(){ 
       var a = 456;
       f1(a);
    } 
    f2();//輸出123
</script>

  這個例子就很值得思考了。

  1.有一個全局做用域的變量a

  2.f1函數調用了a

  3.f2函數先聲明瞭一個局部變量a,而且與全局變量同名

  4.f2函數聲明瞭這個同名的局部變量a以後,又調用了f1函數

  這個程序執行完以後的結果是 : 123 , 而不是456。緣由是js中不存在塊級做用域,只存在函數做用域。

  而函數做用域又被稱之爲詞法做用域,說白了就是:你的函數時在哪兒聲明的,那它就屬於哪兒,也就意味着它只能訪問到這個區間的成員。

  在這個例子中,f1函數是聲明在全局做用域下的,那麼它就屬於全局做用域,也就意味着它(函數體內部)只能訪問到全局做用域下聲明的對象。顯然,當前全局做用域下的a是等於123的,而不是f2內部聲明的那個等於456的a,因此,不論是在什麼時候調用f1這個函數,它訪問到的a必然是那個值等於123的全局變量a。

  那究竟該如何清晰的描述出對象的做用域範圍?這就要使用一個叫作「做用域鏈」的東西了。

  3、做用域鏈

  看這個例子就明白什麼是做用域鏈了。

<script>
    var num = 1;

    function f1(){
        var num = 2;
        function f2(){
            var num = 3;
            function f3(){
                console.log(num);
            }
            f3();
        }
        f2();
    }

    f1(); //輸出 3
    // f2();//報錯,
    // f3();//報錯

</script>

  這個例子就是函數嵌套聲明,而且每一個函數內部還都又聲明瞭一個跟全局變量num同名的局部變量。相信經過剛纔的解釋,你們已經能清晰的得出程序執行結果了。

  簡單說明一下:

  1.在全局做用域下,不能直接調用f2和f3函數,由於它們都是在f1內部嵌套聲明的,它們都是局部做用域聲明的對象,因此不能在全局做用域中訪問。

  2.在局部做用域中,若是出現於更高級的做用域同名的對象,那麼優先訪問本做用域範圍以內的對象

  3.若是在本做用域以內找不到,那麼就往「上一級」做用域中去找,知道全局做用域,若是尚未就報錯。

  那麼這裏所謂的「上一級」做用域,可使用做用域鏈清晰的描繪出來。這個例子的做用域鏈咱們能夠用圖畫出來。

       

  首先這裏的全局做用域咱們稱之爲 0 級做用域,f1函數時在全局做用域下聲明的,因此f1本身造成一個小的做用域,稱之爲 1 級。以此類推。

  當咱們在外部調用f2和f3是不可能的。由於從圖上咱們清晰的看到,在0級做用域的橫線上根本沒有聲明一個叫f2或者f3的對象。

  當咱們在全局(0級)做用域下,調用f1函數,而後逐級的調用到f3的時候,f3內部有一條console.log(num)。此時,程序會沿着咱們畫的線,如今f3的做用域,也就是3級做用下找有沒有聲明一個叫num的對象,若是有就直接拿來打印,若是沒有,則沿着這個做用域鏈往上遞推,首先推到離他最近的上一級做用域,也就是f2的做用域,它是2級做用域,這裏聲明瞭一個叫作num的變量,而且賦了初值爲3,因而,這條打印語句,就馬上把這個=3的num拿來使用,而再也不理會更高級的做用域了。由於它已經找到了一個能夠訪問到的num。

  咱們把這個圖描繪的做用域之間的關係,就形象的稱之爲做用域鏈。

  在做用域鏈中搜索對象,是隻能逐級往上查找的,而不能往低級的做用域中查找。

相關文章
相關標籤/搜索