範圍
全域範圍
局部範圍
範圍鏈
執行內容
使用中的物件
閉包
閉包最佳化
JavaScript中出現了一個以前沒學過的概念——閉包。何為閉包?從表面理解即封閉的包,與範圍有關。所以,說閉包以前先說說範圍。
範圍(scope)
通常來說一段程式碼中使用的變數和函數並不總是可用的,限定其可用性的範圍即範圍,範圍的使用提高了程式邏輯的局部性,增強程式的可靠性,減少名字衝突。
全域範圍(Global Scope)
在代碼中任何地方都能訪問到的對象擁有全域範圍,以下幾種情形擁有全域範圍:
1、最外層函數和在最外層函數外面定義的變數擁有全域範圍,例如: 複製代碼 代碼如下:var outSide="var outside";
function outFunction(){
var name="var inside";
function inSideFunction(){
alert(name);
}
inSideFunction();
}
alert(outSide); //正確
alert(name); //錯誤
outFunction(); //正確
inSideFunction() //錯誤
2、未定義直接賦值的變數自動聲明為擁有全域範圍,例如: 複製代碼 代碼如下:blogName="CSDN李達"
3、所有window對象的屬性擁有全域範圍,例如:window對象的內建屬性都擁有全域範圍,例如window.name、window.location、window.top等
局部範圍(Local Scope) 複製代碼 代碼如下:<span style="font-family: SimSun; ">function outFunction(){
var name="inside name";
function inFunction(){
alert(name);
}
inFunction();
}
alert(name); //錯誤
inFunction(); //錯誤</span>
範圍鏈(scope chain)
JavaScript中,JavaScript裡一切都是對象,包括函數。函數對象和其它對象一樣,擁有可以通過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內部屬性。其中一個內部屬性是範圍,包含了函數被建立的範圍中對象的集合,稱為函數的範圍鏈,它決定了哪些資料能被函數訪問。
當一個函數建立後,它的範圍鏈會被建立此函數的範圍中可訪問的資料對象填充。例如函數: 複製代碼 代碼如下:function add(num1,num2) {
var sum = num1 + num2;
return sum;
}
在函數add建立時,它的範圍鏈中會填入一個全域對象,該全域對象包含了所有全域變數,如所示(注意:圖片只例舉了全部變數中的一部分):
由此可見,函數的範圍鏈是建立函數的時候建立的。
執行內容(Execute context )
函數add的範圍將會在執行時用到,例如:
複製代碼 代碼如下:var total = add(5,10);
當執行 add 函數的時候, JavaScript 會建立一個 Execute context (執行內容),執行內容中就包含了 add 函數運行期所需要的所有資訊。 Execute context 也有自己的 Scope chain, 當函數運行時, JavaScript 引擎會首先從用 add 函數的範圍鏈來初始化執行內容的範圍鏈。
使用中的物件(Active Object)
然後 JavaScript 引擎又會建立一個 Active Object, 這些值按照它們出現在函數中的順序被複製到運行期內容相關的範圍鏈中,它們共同組成了一個新的對象——“使用中的物件(activation object)”,這個對象裡麵包含了函數運行期的所有局部變數,參數以及 this 等變數,此對象會被推入範圍鏈的前端,當運行期上下文被銷毀,使用中的物件也隨之銷毀。新的範圍鏈如所示:
執行內容是一個動態概念,當函數啟動並執行時候建立,使用中的物件 Active Object 也是一個動態概念,它是被執行內容的範圍鏈引用的,可以得出結論:執行內容和使用中的物件都是動態概念,並且執行內容的範圍鏈是由函數範圍鏈初始化的。
在函數執行過程中,每遇到一個變數,都會檢索從哪裡擷取和儲存資料,該過程從範圍鏈頭部,也就是從使用中的物件開始搜尋,尋找同名的標識符,如果找到了就使用這個標識符對應的變數,如果沒有則繼續搜尋範圍鏈中的下一個對象,如果搜尋完所有對象都未找到,則認為該標識符未定義,函數執行過程中,每個標識符都要經曆這樣的搜尋過程。
閉包(closure)
先來看一個執行個體,javascript代碼: 複製代碼 代碼如下:<script type="text/javascript">
function newLoad(){ //建立頁面載入的事件
for (var i = 1; i <=3; i++) {
var anchor = document.getElementById("anchor" + i); //找到每個anchor
anchor.onclick = function () {//為anchor添加單擊事件
alert ("you clicked anchor"+i);//給出點擊反應
}
}
}
window.onload = newLoad; //把newload事件賦值給頁面載入
</script>
前台代碼: 複製代碼 代碼如下:<body>
<a id="anchor1" href="#">anchor1</a><br/>
<a id="anchor2" href="#">anchor2</a><br/>
<a id="anchor3" href="#">anchor3</a><br/>
</body>
運行結果:無論點擊那個anchor,總會彈出anchor4,而我們根本就沒有anchor4:
當我們載入頁面時,javascript中的newLoad函數已經運行完畢,由其中的迴圈可知,anchor已經賦值為4。但是由以前的編程經驗來看,局部變數使用完畢就會銷毀,但是anchor卻沒有,顯然這裡 JavaScript 採用了另外的方式。
閉包在 JavaScript 其實就是一個函數,在函數運行期被建立的,當上面的 函數被執行的時候,會建立一個閉包,而這個閉包會引用newLoad 範圍中的anchor。下面就來看看 JavaScript 是如何來實現閉包的:當執行 newLoad 函數的時候, JavaScript 引擎會建立newLoad函數執行內容的範圍鏈,這個範圍鏈包含了 newLoad執行時的使用中的物件,同時 JavaScript 引擎也會建立一個閉包,而閉包的範圍鏈也會引用 newload的使用中的物件,這樣當 newload執行完的時候,雖然其執行內容和使用中的物件都已經釋放了anchor,但是閉包還是引用著 newload 的使用中的物件,所以點擊顯示的是“you clicked anchor4”。運行期
閉包最佳化
既然閉包出現了我們不想看到的結果,我們需要最佳化它。最佳化後的javascript(其他不變):
複製代碼 代碼如下:<script type="text/javascript">
function newLoad() { //建立頁面載入的事件
for (var i = 1; i <= 3; i++) {
var anchor = document.getElementById("anchor" + i); //找到每個anchor
listener(anchor,i);//listener函數
}
}
function listener(anchor, i) {
anchor.onclick = function () {//為anchor添加單擊事件
alert("you clicked anchor" + i); //給出點擊反應
}
}
window.onload = newLoad; //把newload事件賦值給頁面載入
</script>
運行結果:提示對應的提示資訊
結果分析:最佳化後的結果是因為,我們把聲明的變數和單擊事件相分離。用以上解釋的範圍鏈解釋:頁面載入,先執行listener函數,而listener函數需要anchor變數,在listener函數範圍鏈中沒有,會進入下一級範圍鏈,即尋找newLoad中的anchor,因為在listener中已經確定了哪個anchor單擊對應哪個提示資訊,所以只是在newload中尋找對應的anchor而已,不會等迴圈完畢產生anchor4。
因為接觸javascript時間尚短,理解有誤的地方歡迎指正。