天天看點

第三節:作用域鍊

  JavaScript采用的是靜态作用域規則,也叫詞法作用域,其解析過程是按照從上到下、從左到右的順序加載,并分為兩個階段:預編譯期(預處理)和執行期。預編譯期對代碼塊中所有聲明的變量和函數進行處理。注意關鍵字:代碼塊、聲明、變量、函數。

1、代碼塊

代碼塊是指由<script>标簽分割的代碼段,JavaScript按照代碼塊來進行編譯和執行,代碼塊間互相獨立,但變量和函數共享。

<script type="text/javascript">

    var msg = "我在第一個代碼塊中定義";

    sayHello("張三");

    alert("我可以執行嗎?");

</script>

    alert("第二個代碼塊中調用第一個代碼塊中的變量:\n"+msg);

執行以上代碼,首先報錯:

第三節:作用域鍊

點選是繼續執行:

第三節:作用域鍊

第一個代碼塊中,因為函數sayHello沒有定義自然報錯,程式終止,是以語句alert("我可以執行嗎?")沒有執行,但是第二個代碼塊的代碼仍然可以執行,說明代碼塊間是互相獨立的。而且,第二個代碼塊可以調用第一個代碼塊的變量msg,說明代碼快間的共享性。

是以,JavaScript的執行流程是:

第三節:作用域鍊

2、變量的預處理

預編譯期,變量隻是進行了聲明但未進行初始化以及指派。

    alert(msg);

    var msg = "預處理不會進行初始化";

執行結果為:

第三節:作用域鍊

這裡顯示msg沒有指派,說明變量msg已經聲明了,但沒有初始化指派。如果msg沒有聲明,則應該報錯:msg未定義。

3、函數的預處理

預編譯期隻是對聲明式函數進行處理。

fn();

function fn(){

    alert("我是函數");

}

第三節:作用域鍊

上面的例子說明,在預編譯期聲明式函數已經被處理了,是以即使fn()調用函數放在聲明函數前也能執行。

var fn =function(){

第三節:作用域鍊

而指派式函數卻不會被預處理,是以fn()調用函數放在指派函數前執行就會報錯:缺少對象。再看下面的例子:

    alert("我是函數一");

    alert("我是函數二");

第三節:作用域鍊

兩個同名的聲明式函數都會被預處理,後面的函數覆寫了前面的函數。

var fn = function(){

第三節:作用域鍊

雖然兩個函數同名,但是,因為指派式函數不會被預處理,是以執行的是第一個函數。如果fn()調用函數放在函數的定義之後,那麼:

第三節:作用域鍊

在執行的時候,指派式函數已經被處理了,後面的函數覆寫了前面的函數,是以執行了第二個函數。

4、作用域鍊

執行下面的代碼:

var msg = "我是一個變量";

正常顯示“我是一個變量”,說明内部環境可以通過作用域鍊通路外部環境。

    var msg = "我是一個變量";

alert(msg);

執行報錯“msg未定義”,說明外部環境不能通路内部變量環境中的任何變量和函數。

    msg = "我是一個變量";

這段代碼成功執行,正常顯示“我是一個變量”,說明了什麼呢?

兩段代碼的函數fn有一點細微的差别,第一個函數中代碼var msg = "我是一個變量",有關鍵字var,說明在這裡聲明一個變量并初始化。而第二個函數中的代碼msg = "我是一個變量",少了關鍵字var,說明這裡給變量msg指派。但是,在我們的代碼中并沒有聲明變量msg的語句,那麼,為什麼指派成功了呢?而且後面的語句還以調用這個變量?

在第二段代碼中,我們執行函數fn()的時候,JavaScript引擎在變量表中找不到變量msg,就會沿着作用域鍊一直往上查找,一直到最外圍的全局執行環境,在Web浏覽器中,全局執行環境被認為是window對象。如果都沒找到,那麼,對于讀操作,就會産生運作期錯誤;而對于寫操作,就會等價為 window.msg = "我是一個變量" ,給window對象新增了一個屬性。

是以,第一段代碼中,在函數fn内部聲明了一個變量msg,函數的外部環境無法通路這個變量。而第二段代碼,執行函數fn,其實是給window全局對象設定了一個屬性msg,并指派初始化,是以我們可以通路它。其實,這段代碼标準的寫法應該是:

    window.msg = "我是一個變量";

alert(window.msg);