javascript變量作用域、匿名函數及閉包

一、JavaScript變量作用域(scope)
首先需要明白的幾個要點:
1.JavaScript的變量作用域是基於其特有的作用域鏈的。
2.JavaScript沒有塊級作用域。
3.函數中聲明的變量在整個函數中都有定義。(就後面第三點的說明)

4 .所有在最外層定義(非函數體內定義)的變量都擁有全局作用域
5. 所有末定義直接賦值的變量,系統會自動聲明爲擁有全局作用域的變量
6. 所有window對象的屬性擁有全局作用域

(下面作者還提到全局變量都可以通過window.*來訪問,明白這一點很重要,但是否準確?此外目前存在的疑問還有:

    即使可以以這中方式來訪問全局變量那是否能認爲全局對象由window來統一管理,還是window只是保存了一個拷貝而已?)
    
1.JavaScript的作用域鏈
首先看一個很簡單的例子,下面的代碼中,定義一個全局變量n,n的作用域是全局的。

<script language="javascript" type="text/javascript">
var n=1;
function show(){
   var m=2;
   alert(n);   //顯示結果爲1
}
show();
</script>

運行代碼後,結果爲1。原理就是JavaScript作用域是層層包含的,外層的作用域在內層有效,執行的時候,從內層向外層查找。當函數show執行的時候,首先在函數show內部查找變量n,沒有找到,再向外層查找,找到了全局變量n,alert顯示結果爲1。

2.局部變量優先級高於全局變量

<script language="javascript" type="text/javascript">
var n=1;
function show(){
   var m=2;
   var n=3;
   alert(n);   //顯示結果爲3
}
show();
</script>
這個不難理解,根據上面變量作用域鏈的原理,函數執行時,會先從內層向外層查找,如果在內層中找到變量,查找就會停止。所以內層的作用域會優先於外層,局部優先於全局。
稍改一下:
<script language="javascript" type="text/javascript">
var n=1;
function show(){
   var m=2;
   var n=3;
   alert(window.n);   //顯示結果爲1
}
show();
</script>
爲什麼會是1,而不是3呢,只是另外一個問題:全局變量都是window對象的屬性,任何的全局變量都是window對象的屬性,都可以用window.*來引用。(*代表一個變量)

3.在函數內部使用var聲明的變量,不論在何處聲明,該變量都擁有整個函數的作用域。
<script language="javascript" type="text/javascript">
var n=1;
function show(){
   alert(n);   //undefined
   alert(this.n); //顯示爲1
   var n=3;   //聲明局部變量n
   alert(n);   //顯示爲3
}
show();
</script>

第一個問什麼會顯示undefined呢,這個問題的原因就是在函數後面聲明瞭局部變量n=3,在函數show執行的時候,會先從內層查找變量n,結果找到了,但在第一個alert的時候,n還沒有賦值,故結果爲undefined。

4.JavaScript沒有塊級作用域

<script language="javascript" type="text/javascript">
var n=1;
function show(){
   for(var i=0;i<3;i++){
     var n=6;
     alert(i);   //顯示結果爲0,1,2
   }
   alert("for循環外:"+i); //顯示結果爲:“for循環外:3”
   alert("n="+n);   //顯示結果爲:n=6
}
//alert(i);   外部引用一個未聲明變量會提示出錯
show();
</script>

變量i雖然是在for循環內定義的,但for循環塊外部仍然可以引用,i的作用域是整個函數show。如果在show函數外部引用i變量,會出現JavaScript報錯。

5.沒有使用var聲明的變量屬於全局變量
<script language="javascript" type="text/javascript">
var n=1;
function show(){
   m=9;   //聲明變量m
   var s=6;
}
show();   //不可少,alert之前需要先執行m=9,否則m未聲明
alert(m);   //顯示9
alert(window.m);   //顯示9
alert(s);   //報錯"s is not defined"
</script>

函數show執行的過程中,引用了變量m,但未使用var聲明,m就變成了全局變量。

二、JavaScript匿名函數
所謂匿名函數就是沒有命名的函數,看起來有點難以理解,其實很常見,下面舉一個小例子。

window.οnlοad=function(){ alert("加載完畢!");}

這樣就創建了一個匿名函數,這個function沒有函數名,但可以被調用和執行,當網頁加載完畢時,會執行alert()。

<script language="javascript" type="text/javascript">
(function(x,y){alert(x+y)})(2,3);   //顯示結果爲5
</script>

上面的方法仍然是創建了一個匿名函數,並使用()優先表達式強制執行函數。

三、JavaScript閉包(closure)
閉包其實就是利用了函數作用域和匿名函數的知識,當函數A執行結束時,一部分變量變量被B引用,被引用的變量不能釋放,形成了所謂的閉包。這裏有篇很好的文章,可以參考一下。
下面看一個小例子:
<script language="javascript" type="text/javascript">
function show(){
     var n=3;
     setTimeout(function(){alert("first:"+n);},3000); //3秒後顯示first:3
     alert("second:"+n); //顯示second:3
}
show();
function show2(){alert("第一個函數執行結束");}
show2();
</script>

運行上面的函數後,首先alert顯示"second:3",然後顯示"第一個函數執行結束",3秒後顯示“first:3”。

仔細看一下,函數show執行結束的時候,變量n應該被釋放了,內存裏也就不存在了,怎麼過了3秒還能顯示出"first:3"呢?閉包恰恰就在此產生了,當函數show執行結束的時候,想要釋放內存裏的n,但發現變量n還在被一個匿名函數引用,要在3秒後調用,根據JavaScript閉包原理,內存裏的n被保留了下來,於是在三秒以後仍然可以調用n。
下面看幾種寫法的區別:

function show2(content){
   alert(content);   //3秒後alert顯示"test"
}
function show(content,delay_time){
     setTimeout("show2('"+content+"')",delay_time);
}
show("test",3000);
仔細看一下上面這種寫法不是閉包,只是普通的函數調用而已。
function show(content,delay_time){
     setTimeout(function(){alert(content)},delay_time); //3秒後alert顯示"test"
}
show("test",3000);

這種寫法就是閉包,雖然結果是一樣的,但原理不一樣。
function show(content){
     function show2(){alert(content);}
     return show2;
}
var newshow=show("test");
setTimeout(newshow,3000);   //3秒後alert顯示"test"

上面這種寫法也是閉包,雖然結果都是一樣的,但原理卻不一樣。這裏面看到了函數也可以當作對象也傳遞,函數show執行後return show2,把函數show2賦值給newshow,然後3秒後,調用newshow對象,執行函數show2。
仔細比較上面3種寫法,如果能悟出一些道理就好,閉包有些時候的確難理解。
稍複雜一點的例子:
<script language="javascript" type="text/javascript">
var show=null;
(function(){
   var n=1;
   var show2=function(){
       alert(n++);
   }
   show=show2;
})();

show();   //顯示1
show();   //顯示2
show();   //顯示3
</script>

上面代碼中,又一次用了函數作爲對象傳遞,n是匿名函數裏的局部變量,外部是訪問不到的。show是一個全局變量,應該訪問不了變量n,但在函數show執行前,首先把函數匿名函數內部show2賦值給show,當後面函數show運行的時候,會調用show2的代碼,而show2是可以訪問n的,故結果顯示爲1、2、3。
另外一點值得注意的,也是很經典的一個例子。
CSS代碼:

ul{ width:600px; margin:0px auto;}
ul li{ margin:1px 0px; background-color:#999999;}
HTML代碼:
<ul>
   <li>1</li>
   <li>2</li>
   <li>3</li>
   <li>4</li>
</ul>
JavaScript代碼:
<script language="javascript" type="text/javascript">
var li=document.getElementsByTagName("li");
for(var i=0;i<li.length;i++){
   li[i].οnclick=function(){alert(i);}
}
</script>

運行後,不論點擊哪一個li,都是alert提示“4”。這就是一個需要注意的地方:閉包允許內層函數引用父函數中的變量,但是該變量是最終值。閉包引用的變量i,是循環結束後的值,其實這是一個很常見的問題,解決方法也有很多
比較常見的就是給li[i]添加屬性值,比如li[i].n=i;還有就是用閉包方法解決,這個函數本身就是閉包,所謂用閉包來解決閉包的問題。代碼如下:

<script language="javascript" type="text/javascript">
var li=document.getElementsByTagName("li");
for(var i=0;i<li.length;i++){
   (function(index){
   li[index].οnclick=function(){alert(index);}
   })(i); 
}
</script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章