1. 程式人生 > >深入理解JS中的變數作用域

深入理解JS中的變數作用域

文章轉載http://blog.csdn.net/beijiguangyong/article/details/8301707點選開啟連結

在JS當中一個變數的作用域(scope)是程式中定義這個變數的區域。變數分為兩類:全域性(global)的和區域性的。其中全域性變數的作用域是全域性性的,即在JavaScript程式碼中,它處處都有定義。而在函式之內宣告的變數,就只在函式體內部有定義。它們是區域性變數,作用域是區域性性的。函式的引數也是區域性變數,它們只在函式體內部有定義。

我們可以藉助JavaScript的作用域鏈(scope chain)更好地瞭解變數的作用域。每個JavaScript執行環境都有一個和它關聯在一起的作用域鏈。這個作用域鏈是一個物件列表或物件鏈。當JavaScript程式碼需要查詢變數x的值時(這個過程叫做變數解析(variable name resolution)),它就開始檢視該鏈的第一個物件。如果那個物件有一個名為x的屬性,那麼就採用那個屬性的值。如果第一個物件沒有名為x的屬性,JavaScript就會繼續查詢鏈中的第二個物件。如果第二個物件仍然沒有名為x的屬性,那麼就繼續查詢下一個物件,以此類推。如果查詢到最後(指頂層程式碼中)不存在這個屬性,那麼這個變數的值就是未定義的。

以上的過程並不是我們所熟悉的順序結構,但大致與順序結構類似只不過是將作用域當作一個整體來看待而已。整個過程如上圖所示

JS作用域例項

程式碼一(平淡的不能再平淡的程式碼)

var i=10; 
function a() { 
	alert(i); 
}; 
a();

程式碼二

var i=10; 
function a() { 
    alert(i); 
	var i = 2; 
}; 
a(); 

根據“多年”的程式設計經驗你可能覺得這兩個程式碼輸出是一樣的,但是事實卻是第一個程式碼正常輸出了變數的值----10,而第二個程式碼輸出的卻是undefined。也許很多人理解不了,但是根據前面的作用域鏈的圖我們就很好理解了。

作用域鏈圖中很明確的表示出:在變數解析過程中首先查詢區域性的作用域,然後查詢上層作用域。在程式碼一的函式當中沒有定義變數i,於是查詢上層作用域(全域性作用域),進而進行輸出其值。但是在程式碼二的函式內定義了變數i(無論是在alter之後還是之前定義變數,都認為在此作用域擁有變數i),於是不再向上層的作用域進行查詢,直接輸出i。但是不幸的是此時的區域性變數i並沒有賦值,所以輸出的是undefined。

《JavaScript權威指南》中提出的“沒有塊級作用域”實際上就是上述的意思。很多時候認為懂了、理解了,其實沒有懂,細細的研究一番之後看回過頭來再書中那加粗的文字,頓時恍然大悟,其實人家書裡說的挺清楚的嘛!

程式碼三 某知名公司筆試題

<script type="text/javascript">
	var a,b;
	(function(){
		alert(a);
		alert(b);
		var a = b = 3;
		alert(a);
		alert(b);
	})();
	    alert(a);
            alert(b);
	</script>

結果輸出 undefined undefined 3 3 undefined 3

程式碼等價於

<script type="text/javascript">
	var a,b;
	(function(){
		alert(a);
		alert(b);
		var a = 3;
		b = 3;
		alert(a);
		alert(b);
	})();
	    alert(a);
	    alert(b);
	</script>

這主要是Js中沒有用var宣告的變數都是全域性變數,而且是頂層物件的屬性。

作用域鏈說明

程式碼四:

<script type="text/javascript">
        name="Hello";  
        function t(){  
            var name="world";  
            function s(){  
                var name="welcome";  
                console.log(name);  
            }  
            function ss(){  
                console.log(name);  
            }  
            s();  
            ss();  
        }  
        t(); 
    </script>

函式輸出welcome world

當執行s時,將建立函式s的執行環境(呼叫物件),並將該物件置於連結串列開頭,然後將函式t的呼叫物件連結在之後,最後是全域性物件。然後從連結串列開頭尋找變數name,很明顯

name是"welcome"。

但執行ss()時,作用域鏈是: ss()->t()->window,所以name是”world"

程式碼五:一個很容易犯錯的例子:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"  >
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
    <title>ajax</title>
    <style type="text/css">
    </style>
    <script type="text/javascript">
       function buttonInit(){  
            for(var i=1;i<4;i++){  
                var b=document.getElementById("button"+i);  
                b.addEventListener("click",function(){ alert("Button"+i);},false);  
            }  
        }  
        window.onload=buttonInit;  
    </script>
</head>
<body>
<button id="button1">Button1</button>  
<button id="button2">Button2</button>  
<button id="button3">Button3</button> 
</body> 
</html>

當文件載入完畢,給幾個按鈕註冊點選事件,當我們點選按鈕時,會彈出什麼提示框呢?

很容易犯錯,對是的,三個按鈕都是彈出:"Button4",你答對了嗎?

當註冊事件結束後,i的值為4,當點選按鈕時,事件函式即function(){ alert("Button"+i);}這個匿名函式中沒有i,根據作用域鏈,所以到buttonInit函式中找,此時i的值為4,

所以彈出”button4“。