JavaScript高級之詞法做用域和做用域鏈

主要內容:數組

  • 分析JavaScript的詞法做用域的含義
  • 解析變量的做用域鏈
  • 變量名提高時什麼

1、關於塊級做用域
        說到JavaScript的變量做用域,與我們平時使用的類C語言不一樣.
例如C#中下面代碼:閉包

  1. static void Main(string[] args)
  2. {
  3.         if(true)
  4.         {
  5.                 int num = 10;
  6.         }
  7.         System.Console.WriteLine(num);
  8. }

這段代碼若是進行編譯,是沒法經過的,由於"當前上下文中不存在名稱num". 由於這裏
變量的做用域是由花括號限定的,稱爲塊級做用域.
        在塊級做用域下,全部的變量都在定義的花括號內,從定義開始到花括號結束這個
範圍內可使用. 出了這個範圍就沒法訪問. 也就是說代碼函數

  1. if(true)
  2. {
  3.         int num = 10;
  4.         System.Console.WriteLine(num);
  5. }

這裏能夠訪問,由於變量的定義與使用在同一個花括號內.
        可是在JavaScript中就不同,JavaScript中沒有塊級做用域的概念.
2、JavaScript中的做用域
        在JavaScript中,下面代碼:spa

  1. if(true) {
  2.         var num = 10;
  3. }
  4. alert(num);

運行的結果是彈窗10. 那麼在JavaScript中變量的做用範圍是怎麼限定的呢?
2.1 函數限定變量做用域
        在JavaScript中,只有函數能夠限定一個變量的做用範圍. 什麼意思呢?
就是說,在JavaScript中,在函數裏面定義的變量,能夠在函數裏面被訪問,可是在函數外
沒法訪問. 看以下代碼:對象

  1. var func = function() {
  2.         var num = 10;
  3. };
  4. try {
  5.         alert(num);
  6. } catch ( e ) {
  7.         alert( e );
  8. }

這段代碼運行時,會拋出一個異常,變量num沒有定義. 也就是說,定義在函數中的變量沒法
在函數外使用,固然在函數內能夠隨意的使用, 即便在賦值以前. 看下面代碼:ip

  1. var func = function() {
  2.         alert(num);
  3.         var num = 10;
  4.         alert(num);
  5. };
  6. try {
  7.         func();
  8. } catch ( e ) {
  9.         alert( e );
  10. }

這段代碼運行後,不會拋出錯誤,彈窗兩次,分別是 undefined 和 10(至於爲何,下文解釋).
        從這裏能夠看得出,變量只有在函數中能夠被訪問. 同理在該函數中的函數也能夠訪問.
2.2 子域訪問父域
        前面說了,函數能夠限定變量的做用域,那麼在函數中的函數就成爲該做用域的子域. 在子域
中的代碼能夠訪問到父域中的變量. 看下面代碼:作用域

  1. var func = function() {
  2.         var num = 10;
  3.         var sub_func = function() {
  4.                 alert(num);
  5.         };
  6.         sub_func();
  7. };
  8. func();

複製代碼開發

這段代碼執行獲得的結果就是 10. 能夠看到上文所說的變量訪問狀況. 可是在子域中訪問父域的
代碼也是有條件的. 以下面代碼:string

  1. var func = function() {
  2.         var num = 10;
  3.         var sub_func = function() {
  4.                 var num = 20;
  5.                 alert(num);
  6.         };
  7.         sub_func();
  8. };
  9. func();

這段代碼比前面就多了一個"var num = 20;",這句代碼在子域中,那麼子域訪問父域的狀況就發
生了變化,這段代碼打印的結果是 20. 即此時子域訪問的num是子域中的變量,而不是父域中的.
        因而可知訪問有必定規則可言. 在JavaScript中使用變量,JavaScript解釋器首先在當前做
用域中搜索是否有該變量的定義,若是有,就是用這個變量;若是沒有就到父域中尋找該變量.
以此類推,直到最頂級做用域,仍然沒有找到就拋出異常"變量未定義". 看下面代碼:it

  1. (function() {
  2.         var num = 10;
  3.         (function() {
  4.                 var num = 20;
  5.                 (function(){
  6.                         alert(num);
  7.                 })()
  8.         })();
  9. })();

這段代碼執行後打印出20. 若是將"var num = 20;"去掉,那麼打印的就是10. 一樣,若是再去掉
"var num = 10",那麼就會出現未定義的錯誤.
3、做用域鏈
        有了JavaScript的做用域的劃分,那麼能夠將JavaScript的訪問做用域連成一個鏈式樹狀結構.
JavaScript的做用域鏈一旦能清晰的瞭解,那麼對於JavaScript的變量與閉包就是很是清晰的了.
下面採用繪圖的辦法,繪製做用域鏈.
3.1 繪製規則:
        1) 做用域鏈就是對象的數組
        2) 所有script是0級鏈,每一個對象佔一個位置
        3) 凡是看到函數延伸一個鏈出來,一級級展開
        4) 訪問首先看當前函數,若是沒有定義往上一級鏈檢查
        5) 如此往復,直到0級鏈

3.2 舉例
        看下面代碼:

  1. var num = 10;
  2. var func1 = function() {
  3.         var num = 20;
  4.         var func2 = function() {
  5.                 var num = 30;
  6.                 alert(num);
  7.         };
  8.         func2();
  9. };
  10. var func2 = function() {
  11.         var num = 20;
  12.         var func3 = function() {
  13.                 alert(num);
  14.         };
  15.         func3();
  16. };
  17. func1();
  18. func2();

下面分析一下這段代碼:
        -> 首先整段代碼是一個全局做用域,能夠標記爲0級做用域鏈,那麼久有一個數組
                var link_0 = [ num, func1, func2 ];                // 這裏用僞代碼描述
        -> 在這裏func1和func2都是函數,所以引出兩條1級做用域鏈,分別爲
                var link_1 = { func1: [ num, func2 ] };        // 這裏用僞代碼描述
                var link_1 = { func2: [ num, func3 ] };        // 這裏用僞代碼描述
        -> 第一條1級鏈衍生出2級鏈
                var link_2 = { func2: [ num ] };        // 這裏用僞代碼描述
        -> 第二條1級鏈中沒有定義變量,是一個空鏈,就表示爲
                var link_2 = { func3: [ ] };
        -> 將上面代碼整合一下,就能夠將做用域鏈表示爲:
                // 這裏用僞代碼描述
                var link = [ // 0級鏈
                        num,
                        { func1 : [        // 第一條1級鏈
                                                num,
                                                { func2 : [        // 2級鏈
                                                                        num
                                                                ] }
                                        ]},
                        { func2 : [        // 第二條1級鏈
                                                num,
                                                { func3 : [] }
                                        ]}
                ];
        -> 用圖像表示爲

                圖:01_01做用域鏈.gif
        注意:將鏈式的圖用js代碼表現出來,再有高亮顯示的時候就很是清晰了.
有了這個做用域鏈的圖,那麼就能夠很是清晰的瞭解訪問變量是如何進行的:
在須要使用變量時,首先在當前的鏈上尋找變量,若是找到就直接使用,不會
向上再找;若是沒有找到,那麼就向上一級做用域鏈尋找,直到0級做用域鏈.

        若是能很是清晰的肯定變量所屬的做用域鏈的級別,那麼在分析JavaScript
代碼與使用閉包等高級JavaScript特性的時候就會很是容易(至少我是這樣哦).
4、變量名提高與函數名提高
        有了做用域鏈與變量的訪問規則,那麼就有一個很是棘手的問題. 先看下面
的JavaScript代碼:

  1. var num = 10;
  2. var func = function() {
  3.         alert(num);
  4.         var num = 20;
  5.         alert(num);
  6. };
  7. func();

執行結果會是什麼呢?你能夠想想,我先不揭曉答案.
        先來分析一下這段代碼.
        這段代碼中有一條0級做用域鏈,裏面有成員num和func. 在func下是1級做用
域鏈,裏面有成員num. 所以在調用函數func的時候,就會檢測到在當前做用域中
變量num是定義過的,因此就會使用這個變量. 可是此時num並無賦值,由於代
碼是從上往下運行的. 所以第一次打印的是 undefined,而第二次打印的即是20.
        你答對了麼?
        像這樣將代碼定義在後面,而在前面使用的狀況在JavaScript中也是常見的
問題. 這時就好像變量在一開始就定義了同樣,結果就如同下面代碼:

  1. var num = 10;
  2. var func = function() {
  3.         var num;        // 感受就是這裏已經定義了,可是沒有賦值同樣
  4.         alert(num);
  5.         var num = 20;
  6.         alert(num);
  7. };
  8. func();

那麼這個現象經常稱爲變量名提高. 一樣也有函數名提高這一說. 以下面代碼:

  1. var func = function() {
  2.         alert("調用外面的函數");
  3. };
  4. var foo = function() {
  5.         func();
  6.         var func = function() {
  7.                 alert("調用內部的函數");
  8.         };
  9.         func();
  10. };

好了,這段代碼結果如何?或則應該有什麼不同,我先不說沒留着讀者思考吧!
下一篇再作解答.
        因爲有了這些不一樣,所以在實際開發的時候,推薦將變量都寫在開始的地方,
也就是在函數的開頭將變量就定義好,相似於C語言的規定同樣. 這個在js庫中也
是這麼完成的,如jQuery等.

5、小結
        好了這篇文章主要是說明JavaScript的詞法做用域是怎麼一回事兒,以及解釋
如何分析做用域鏈,和變量的訪問狀況。

 

轉自傳智播客,講的不錯,容易理解!

相關文章
相關標籤/搜索