1. 程式人生 > >第三節:作用域鏈

第三節:作用域鏈

靜態作用域規則 變量和函數的預處理 作用域鏈

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


1、代碼塊


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


<script type="text/javascript">

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

sayHello("張三");

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

</script>

<script type="text/javascript">

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

</script>


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


技術分享

點擊是繼續執行:

技術分享

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


因此,JavaScript的執行流程是:


技術分享


2、變量的預處理


預編譯期,變量只是進行了聲明但未進行初始化以及賦值。

alert(msg);

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

執行結果為:

技術分享

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


3、函數的預處理


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


fn();

function fn(){

alert("我是函數");

}

技術分享

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


fn();

var fn =function(){

alert("我是函數");

}

技術分享

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


fn();

function fn(){

alert("我是函數一");

}

function fn(){

alert("我是函數二");

}

技術分享


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


fn();

function fn(){

alert("我是函數一");

}

var fn = function(){

alert("我是函數二");

}

技術分享


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


function fn(){

alert("我是函數一");

}

var fn = function(){

alert("我是函數二");

}

fn();

技術分享


在執行的時候,賦值式函數已經被處理了,後面的函數覆蓋了前面的函數,所以執行了第二個函數。


4、作用域鏈


執行下面的代碼:


function fn(){

alert(msg);

}

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

fn();


正常顯示“我是一個變量”,說明內部環境可以通過作用域鏈訪問外部環境。


function fn(){

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

}

alert(msg);


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


function fn(){

msg = "我是一個變量";

}

fn();

alert(msg);


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


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


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


所以,第一段代碼中,在函數fn內部聲明了一個變量msg,函數的外部環境無法訪問這個變量。而第二段代碼,執行函數fn,其實是給window全局對象設置了一個屬性msg,並賦值初始化,所以我們可以訪問它。其實,這段代碼標準的寫法應該是:


function fn(){

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

}

fn();

alert(window.msg);


本文出自 “老惠” 博客,轉載請與作者聯系!

第三節:作用域鏈