作用域分配與變量訪問規則
在 ECMAScript 中,函數也是對象。函數對象在變量實例化過程中會根據函數聲明來創建,或者是在計算函數表達式或調用 Function 構造函數時創建。(關於'函數對象'請見《理解Javascript_08_函數對象》)。每個函數對象都有一個內部的 [[scope]] 屬性,這個屬性也由對象列表(鏈)組成。這個內部的[[scope]] 屬性引用的就是創建它們的執行環境的作用域鏈,同時,當前執行環境的活動對象被添加到該對象列表的頂部。當我們在函數內部訪問變量時,其實就是在作用域鏈上尋找變量的過程。
理論性太強了(總結死我了!),還是讓我們來看一段代碼吧:
復制代碼 代碼如下:
<script type="text/javascript">
function outer(){
var i = 10;
function inner(){
var j = 100;
alert(j);//100
alert(i);//10
alert(adf);
}
inner();
}
outer();
</script>
下圖清晰的展現了上述代碼的內存分配與作用域分配情況:
下面來解釋一下:
1.載入代碼,創建全局執行環境,此時會在可變對象(window)中添加outer變量,其指向於函數對象outer,此時作用域鏈中只有window對象.
2.執行代碼,當程序執行到outer()時,會在全局對象中尋找outer變量,成功調用。
3.創建outer的執行環境,此時會新創建一個活動對象,添加變量i,設置值為10,添加變量inner,指向於函數對象inner.並將活動對象壓入作用域鏈中.並將函數對象outer的[[scope]]屬性指向活動對象outer。此時作用域鏈為outer的活動對象+window.
4.執行代碼,為 i 成功賦值。當程序執行到inner()時,會在函數對象outer的[[scope]]中尋找inner變量。找到後成功調用。
5.創建inner的執行環境,新建一個活動對象,添加變量j,賦值為100,並將該活動對象壓入作用域鏈中,並函數對象inner的[[scope]]屬性指向活動對象inner.此時作用域鏈為:inner的活動對象+outer的活動對象+全局對象.
6.執行代碼為j賦值,當訪問i、j時成功在作用域中找到對應的值並輸出,而當訪問變量adf時,沒有在作用域中尋找到,訪問出錯。
注:通過內存圖,我們會發現作用域鏈與prototype鏈是如此的相象。這說明了很多問題...(仁者見仁智者見智,自己探尋答案吧!)
閉包原理
在我們了解了作用域的問題之後,對於閉包這個問題已經很簡單了。什麼是閉包?閉包就是封閉了外部函數作用域中變量的內部函數。
我們來看一個典型的閉包運用:生成increment值
復制代碼 代碼如下:
<script type="text/javascript">
var increment = (function(){
var id = 0;
return function(){
return ++id;
}
})()
alert(increment());//1
alert(increment());//2
</script>
外層匿名函數返回的是一個內嵌函數,內嵌函數使用了外層匿名函數的局部變量id。照理外層匿名函數的局部變量在返回時就超出了作用域因此increment()調用無法使用才對。這就是閉包Closure,即函數調用返回了一個內嵌函數,而內嵌函數引用了外部函數的局部變量、參數等這些應當被關閉(Close)了的資源。這是怎麼一回事呢?讓我們來尋找答案:
根據Scope Chain的理解可以解釋,返回的內嵌函數已經持有了構造它時的Scope Chain,雖然outer返回導致這些對象超出了作用域、生存期范圍,但JavaScript使用自動垃圾回收來釋放對象內存: 按照規則定期檢查,對象沒有任何引用才被釋放。因此上面的代碼能夠正確運行。
參考:
http://www.cnblogs.com/RicCC/archive/2008/02/15/JavaScript-Object-Model-Execution-Model.html
http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html