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);