閉包主要涉及到js的幾個其餘的特性:做用域鏈,垃圾(內存)回收機制,函數嵌套,等等
1、
做用域鏈:函數在定義的時候建立的,用於尋找使用到的變量的值的一個索引,而他內部的規則是,把函數自身的本地變量放在最前面,把自身的父級函數中的變量放在其次,把再高一級函數中的變量放在更後面,以此類推直至全局對象爲止.當函數中須要查詢一個變量的值的時候,js解釋器會去做用域鏈去查找,從最前面的本地變量中先找,若是沒有找到對應的變量,則到下一級的鏈上找,一旦找到了變量,則再也不繼續.若是找到最後也沒找到須要的變量,則解釋器返回undefined。
2、
內存回收機制:一個函數在執行開始的時候,會給其中定義的變量劃份內存空間保存,以備後面的語句所用,等到函數執行完畢返回了,這些變量就被認爲是無用的了.對應的內存空間也就被回收了.下次再執行此函數的時候,全部的變量又回到最初的狀態,從新賦值使用.可是若是這個函數內部又嵌套了另外一個函數,而這個函數是有可能在外部被調用到的.而且這個內部函數又使用了外部函數的某些變量的話.這種內存回收機制就會出現問題.若是在外部函數返回後,又直接調用了內部函數,那麼內部函數就沒法讀取到他所須要的外部函數中變量的值了.因此js解釋器在遇到函數定義的時候,會自動把函數和他可能使用的變量(包括本地變量和父級和祖先級函數的變量(自由變量))一塊兒保存起來.也就是構建一個閉包,這些變量將不會被內存回收器所回收,只有當內部的函數不可能被調用之後(例如被刪除了,或者沒有了指針),纔會銷燬這個閉包,而沒有任何一個閉包引用的變量纔會被下一次內存回收啓動時所回收。
3、
局部變量&全局變量
一、全局(global)變量的做用域是全局的,在Javascript中到處有定義;而函數內部聲明的變量是局部(local)變量,其做用域是局部性的,只在函數體內部有定義,每次執行該函數時都會建立和破壞該變量。
二、全局變量做用域中使用變量能夠不用var語句,但在聲明局部變量是必定要使用var語句,不然會視爲對全局變量的引用。
三、
var scope = "local";聲明的變量在整個checkScope函數做用域內都有效,所以第一個
document.write(scope);執行的時scope引用的是局部變量,而此時局部變量scope還沒有定義,因此輸出」undefined」。好的編程習慣是將全部的變量聲明集中起來放在函數的開頭。document.write(window.scope)//輸出global
全局變量老是存在於運行期上下文做用域鏈的最末端,所以在標識符解析的時候,查找全局變量是最慢的。因此,在編寫代碼的時候應儘可能少使用全局變量,儘量使用局部變量。一個好的經驗法則是:若是一個跨做用域的對象被引用了一次以上,則先把它存儲到局部變量裏再使用(document、window等)。
在執行JavaScript代碼的過程當中,當遇到一個標識符,就會根據標識符的名稱,在執行上下文(Execution Context)的做用域鏈中進行搜索。從做用域鏈的第一個對象(該函數的Activation Object對象)開始,若是沒有找到,就搜索做用域鏈中的下一個對象,如此往復,直到找到了標識符的定義。若是在搜索完做用域中的最後一個對象,也就是全局對象(Global Object)之後也沒有找到,則會拋出一個錯誤,提示用戶該變量未定義(undefined)。這是在ECMA-262標準中描述的函數執行模型和標識符解析(Identifier Resolution)的過程。
由ECMA-262標準第三版定義,該內部屬性包含了函數被建立的做用域中對象的集合,這個集合被稱爲函數的做用域鏈,它決定了哪些數據能被函數訪問。做用域第一個對象始終是當前執行代碼所在環境的變量對象
function a(x,y){
var b=x+y;
return b;
}
在函數a建立的時候它的做用域鏈填入全局對象,全局對象中有全部全局變量
var tatal=a(5,10);
執行此函數時會建立一個稱爲「運行期上下文(execution context)」的內部對象,運行期上下文定義了函數執行時的環境。值按照它們出如今函數中的順序被複制到運行期上下文的做用域鏈中。它們共同組成了一個新的對象,叫「活動對象(activation object)」,該對象包含了函數的全部局部變量、命名參數、參數集合以及this,而後此對象會被推入做用域鏈的前端,當運行期上下文被銷燬,活動對象也隨之銷燬。
ECMAScript變量可能包含兩種不一樣數據類型的值:基本類型值和引用類型值。基本類型值指的是那些保存在棧內存中的簡單數據段,即這種值徹底 保存在內存中的一個位置。而引用類型值是指那些保存堆內存中的對象,意思是變量中保存的實際上只是一個指針,這個指針指向內存中的另外一個位置,該位置保存對象。
5種基本數據類型:Undefined、Null、Boolean、 Number和String。這5種基本數據類型的值在內存中分別佔有固定大小的空間,所以能夠把它們的值保存在棧內存。
若是賦給變量的是一個引用類型的值,則必須在堆內存中爲這個值分配空間。因爲這種值的大小不固定,所以不能把它們保存到棧內存中。但內存地址的大小 是固定的,所以能夠將內存地址保存在棧內存中。這樣,當查詢引用類型的變量時,就能夠首先從棧中讀取內存地址,而後再「順藤摸瓜」地找到保存在堆中的值。
保存在棧內存中的每一個值,分別佔據着固定大小的空間,能夠按照順序來訪問它們。若是棧內存中保存的是一塊內存的地址,則這個值就像是一個指向對象在堆內存中位置的指針。保存在堆內存中的數據不是按順序訪問的,由於每一個對象所須要的空間並不相等。
當從一個變量向另外一個變量複製引用類型的值時,一樣也會將儲存在棧中的值複製一份放到爲新變量分配的空間中。不一樣的是,
這個值的副本其實是一個指針,而這個指針指向存儲在堆中的一個對象。複製操做結束後,兩個變量實際上將引用同一個對象。所以,改變其中一個變量,就會影響到另外一個變量。
typeof操做符是肯定一個變量是字符串、數值、布爾 值,仍是undefined基本數據類型的最佳工具。檢測引用類型的值時,ECMAScript提供了
instanceof操做符。
4、閉包
只要存在調用內部函數的可能,JavaScript就須要保留被引用的函數。並且JavaScript運行時須要跟蹤引用這個內部函數的全部變量,直到最後一個變量廢棄,JavaScript的垃圾收集器才能釋放相應的內存空間(紅色部分是理解閉包的關鍵)。
閉包最大用處有兩個,一個是能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中。
1)因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。
2)閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象(object)使用,把閉包看成它的公用方法(Public Method),把內部變量看成它的私有屬性(private value),這時必定要當心,不要隨便改變父函數內部變量的值。
閉包一些例子:
上面代碼在頁面加載後就會執行,當i的值爲4的時候,判斷條件不成立,for循環執行完畢,可是由於每一個span的onclick方法這時候爲內部函數,因此i被閉包引用,內存不能被銷燬,i的值會一直保持4,直到程序改變它或者全部的onclick函數銷燬(主動把函數賦爲null或者頁面卸載)時纔會被回收。這樣每次咱們點擊span的時候,onclick函數會查找i的值(做用域鏈是引用方式),一查等於4