JavaScript中的做用域鏈和閉包

JavaScript中的做用域鏈和閉包

    JavaScript中出現了一個之前沒學過的概念——閉包。何爲閉包?從表面理解即封閉的包,與做用域有關。因此,說閉包之前先說說做用域。javascript

做用域(scope)html

    一般來講一段程序代碼中使用的變量和函數並不老是可用的,限定其可用性的範圍即做用域,做用域的使用提升了程序邏輯的局部性,加強程序的可靠性,減小名字衝突。前端

    全局做用域(Global Scope)java

    在代碼中任何地方都能訪問到的對象擁有全局做用域,如下幾種情形擁有全局做用域:編程

    一、最外層函數和在最外層函數外面定義的變量擁有全局做用域,例如:閉包

[javascript]  view plain copy print ?
 
  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">var outSide="var outside";  
  2. function outFunction(){  
  3.     var name="var inside";  
  4.     function inSideFunction(){  
  5.         alert(name);  
  6.     }  
  7.     inSideFunction();  
  8. }  
  9. alert(outSide); //正確  
  10. alert(name); //錯誤  
  11. outFunction(); //正確  
  12. inSideFunction() //錯誤</span>  

    二、未定義直接賦值的變量自動聲明爲擁有全局做用域,例如:ide

 

[javascript]  view plain copy print ?
 
  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">blogName="CSDN李達"</span>  
    三、全部window對象的屬性擁有全局做用域,例如:window對象的內置屬性都擁有全局做用域,例如window.name、window.location、window.top等

 

局部做用域(Local Scope)函數

[javascript]  view plain copy print ?
 
  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">function outFunction(){  
  2.          var name="inside name";  
  3.         function inFunction(){  
  4.                 alert(name);  
  5.          }  
  6.          inFunction();  
  7. }  
  8. alert(name); //錯誤  
  9. inFunction(); //錯誤</span>  
做用域鏈(scope chain)

 

    JavaScript中,JavaScript裏一切都是對象,包括函數。函數對象和其它對象同樣,擁有能夠經過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內部屬性。其中一個內部屬性是做用域,包含了函數被建立的做用域中對象的集合,稱爲函數的做用域鏈,它決定了哪些數據能被函數訪問。
  當一個函數建立後,它的做用域鏈會被建立此函數的做用域中可訪問的數據對象填充。例如函數:post

[javascript]  view plain copy print ?
 
  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">function add(num1,num2) {  
  2.     var sum = num1 + num2;  
  3.     return sum;  
  4. }</span>  

    在函數add建立時,它的做用域鏈中會填入一個全局對象,該全局對象包含了全部全局變量,以下圖所示(注意:圖片只例舉了所有變量中的一部分):優化

 

    因而可知,函數的做用域鏈是建立函數的時候建立的。

執行上下文(Execute context )

    函數add的做用域將會在執行時用到,例如:

 

[javascript]  view plain copy print ?
 
  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">var total = add(5,10);</span>  

 

    當執行 add 函數的時候, JavaScript 會建立一個 Execute context (執行上下文),執行上下文中就包含了 add 函數運行期所須要的全部信息。 Execute context 也有本身的 Scope chain, 當函數運行時, JavaScript 引擎會首先從用 add 函數的做用域鏈來初始化執行上下文的做用域鏈。

活動對象(Active Object)

    而後 JavaScript 引擎又會建立一個 Active Object, 這些值按照它們出如今函數中的順序被複制到運行期上下文的做用域鏈中,它們共同組成了一個新的對象——「活動對象(activation object)」,這個對象裏面包含了函數運行期的全部局部變量,參數以及 this 等變量,此對象會被推入做用域鏈的前端,當運行期上下文被銷燬,活動對象也隨之銷燬。新的做用域鏈以下圖所示:

    執行上下文是一個動態的概念,當函數運行的時候建立,活動對象 Active Object 也是一個動態的概念,它是被執行上下文的做用域鏈引用的,能夠得出結論:執行上下文和活動對象都是動態概念,而且執行上下文的做用域鏈是由函數做用域鏈初始化的。

    在函數執行過程當中,每遇到一個變量,都會檢索從哪裏獲取和存儲數據,該過程從做用域鏈頭部,也就是從活動對象開始搜索,查找同名的標識符,若是找到了就使用這個標識符對應的變量,若是沒有則繼續搜索做用域鏈中的下一個對象,若是搜索完全部對象都未找到,則認爲該標識符未定義,函數執行過程當中,每一個標識符都要經歷這樣的搜索過程。

閉包(closure)

        先來看一個實例,javascript代碼:

[javascript]  view plain copy print ?
 
  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">    <script type="text/javascript">  
  2.     function newLoad(){ //新建頁面加載的事件  
  3.         for (var i = 1; i <=3; i++) {  
  4.                 var anchor = document.getElementById("anchor" + i); //找到每一個anchor  
  5.                 anchor.onclick = function () {//爲anchor添加單擊事件  
  6.                         alert ("you clicked anchor"+i);//給出點擊反應  
  7.                 }  
  8.         }  
  9. }  
  10. window.onload = newLoad; //把newload事件賦值給頁面加載  
  11.     </script></span>  
    前臺代碼:
[html]  view plain copy print ?
 
  1. <span style="font-family:KaiTi_GB2312;font-size:18px;"><body>  
  2. <id="anchor1" href="#">anchor1</a><br/>  
  3. <id="anchor2" href="#">anchor2</a><br/>  
  4. <id="anchor3" href="#">anchor3</a><br/>  
  5. </body></span>  
    運行結果:不管點擊那個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(其餘不變):

[javascript]  view plain copy print ?
 
  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">     <script type="text/javascript">  
  2.             function newLoad() { //新建頁面加載的事件  
  3.                     for (var i = 1; i <= 3; i++) {  
  4.                             var anchor = document.getElementById("anchor" + i); //找到每一個anchor  
  5.                             listener(anchor,i);//listener函數  
  6.                             }  
  7.                     }  
  8.                 function listener(anchor, i) {  
  9.                         anchor.onclick = function () {//爲anchor添加單擊事件  
  10.                                 alert("you clicked anchor" + i); //給出點擊反應  
  11.                         }  
  12.                }  
  13.             window.onload = newLoad; //把newload事件賦值給頁面加載  
  14.     </script></span>  
    運行結果:提示對應的提示信息

 

    

    結果分析:優化後的結果是由於,咱們把聲明的變量和單擊事件相分離。用以上解釋的做用域鏈解釋:頁面加載,先執行listener函數,而listener函數須要anchor變量,在listener函數做用域鏈中沒有,會進入下一級做用域鏈,即查找newLoad中的anchor,由於在listener中已經肯定了哪一個anchor單擊對應哪一個提示信息,因此只是在newload中查找對應的anchor而已,不會等循環完畢產生anchor4。

    由於接觸javascript時間尚短,理解有誤的地方歡迎指正。

 1:【閉包在 JavaScript 其實就是一個函數,在函數運行期被建立的,當上面的 函數被執行的時候,會建立一個閉包,而這個閉包會引用newLoad 做用域中的anchor。】
應該是:當上面的 函數被執行的時候,會建立一個閉包,而這個閉包會引用newLoad 做用域中的i
2:【結果分析:優化後的結果是由於,咱們把聲明的變量和單擊事件相分離。用以上解釋的做用域鏈解釋:頁面加載,先執行listener函數,而listener函數須要anchor變量,在listener函數做用域鏈中沒有,會進入下一級做用域鏈,即查找newLoad中的anchor,由於在listener中已經肯定了哪一個anchor單擊對應哪一個提示信息,因此只是在newload中查找對應的anchor而已,不會等循環完畢產生anchor4。】應該是:listener(anchor,i);函數3次執行會產生3個不一樣的閉包,分別引用不一樣的anchor 和i。因此執行 anchor.onclick = function () { alert("you clicked anchor" + i);} 時會根據3個不一樣的"執行上下文"的做用域鏈而找到不一樣的i變量。
3:總結:子函數會根據擁有當初時刻"執行上下文對象"的做用域鏈的閉包而找到當初時刻的存儲的一系類值。
4:閉包算是執行時動態的分配並保持對當時狀態的記憶存儲。
5:函數每次執行時對應的運行期上下文都是獨一無二的,因此屢次調用同一個函數就會致使建立多個運行期上下文,當函數執行完畢,執行上下文會被銷燬。每個運行期上下文都和一個做用域鏈關聯。
相關文章
相關標籤/搜索