閉包(closure)是javascript
的一大難點,也是它的特點。不少高級應用都要依靠閉包來實現。javascript
要理解閉包,首先要理解javascript
的特殊的變量做用域。java
變量的做用域無非就兩種:全局變量和局部變量。數組
javascript
語言的特別之處就在於: 函數內部能夠直接讀取全局變量,可是在函數外部沒法讀取函數內部的局部變量。安全
注意點:在函數內部聲明變量的時候,必定要使用var命令。 若是不用的話,你實際上聲明的是一個全局變量!閉包
出於種種緣由,咱們有時候須要獲取到函數內部的局部變量。可是,上面已經說過了,正常狀況下,這是辦不到的!只有經過變通的方法才能實現。dom
那就是在函數內部,再定義一個函數。模塊化
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
複製代碼
在上面的代碼中,函數f2就被包括在函數f1內部,這時f1內部的全部局部變量,對f2都是可見的。可是反過來就不行,f2內部的局部變量,對f1就是不可見的。函數
這就是Javascript語言特有的 "鏈式做用域"結構(chain scope), 子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立。性能
既然f2能夠讀取f1中的局部變量,那麼只要把f2做爲返回值,咱們不就能夠在f1外部讀取它的內部變量了嗎!ui
上面代碼中的f2函數,就是閉包。
各類專業文獻的閉包定義都很是抽象,個人理解是: 閉包就是可以讀取其餘函數內部變量的函數。
因爲在javascript中,只有函數內部的子函數才能讀取局部變量,因此說,閉包能夠簡單理解成「定義在一個函數內部的函數「。
因此,在本質上,閉包是將函數內部和函數外部鏈接起來的橋樑。
一、函數做爲返回值,二、函數做爲參數傳遞。
function fn() {
var max = 10;
return function bar(x) {
if(x > max){
console.log(x)
}
}
}
複製代碼
如上代碼,bar函數做爲返回值,賦值給f1變量。執行f1(15)時,用到了fn做用域下的max變量的值。
var max = 10;
var fn = function(x) {
if(x > max){
console.log(x)
}
}
(function(f) {
var max = 10;
f(15)
})(fn)
複製代碼
如上代碼中,fn函數做爲一個參數被傳遞進入另外一個函數,賦值給f參數。執行f(15)時,max變量的取值是10,而不是100。
閉包能夠用在許多地方。它的最大用處有兩個,一個是前面提到的能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中,不會在f1調用後被自動清除。
爲何會這樣呢?緣由就在於f1是f2的父函數,而f2被賦給了一個全局變量,這致使f2始終在內存中,而f2的存在依賴於f1,所以f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。
JS中定義一個函數,最經常使用的就是函數聲明和函數表達式
Js中的函數聲明是指下面的形式:
function functionName(){
}
複製代碼
函數表達式則是相似表達式那樣來聲明一個函數:
var functionName = function(){
}
複製代碼
咱們可使用函數表達式建立一個函數並立刻執行它,如:
(function() {
var a, b // local variables
// ... // and the code
})()
複製代碼
()();
第一個括號裏放一個無名的函數。
兩者區別:js的解析器對函數聲明與函數表達式並非一視同仁地對待的。對於函數聲明,js解析器會優先讀取,確保在全部代碼執行以前聲明已經被解析,而函數表達式,如同定義其它基本類型的變量同樣,只在執行到某一句時也會對其進行解析,因此在實際中,它們仍是會有差別的,具體表如今,當使用函數聲明的形式來定義函數時,可將調用語句寫在函數聲明以前,然後者,這樣作的話會報錯。
一、模塊化代碼
使用自執行的匿名函數來模擬塊級做用域
(function(){
// 這裏爲塊級做用域
})();
複製代碼
該方法常常在全局做用域中被用在函數外部,從而限制向全局做用域中添加過多的變量和函數影響全局做用域。也能夠減小如閉包這樣的對內存的佔用,因爲匿名函數沒有變量指向,執行完畢就能夠當即銷燬其做用域鏈。
var test = (function(){
var a= 1;
return function(){
a++;
alert(a);
}
})();
test();//2
test();//3
複製代碼
實現a的自加,不污染全局。
二、循環閉包
循環給每一個li註冊一個click事件,點擊alert序號。代碼以下:
var aLi = document.getElementByClassName("test");
function showAllNum( aLi ){
for( var i =0,len = aLi.length ;i<len;i++ ){
aLi[i].onclick = function(){
alert( i );//all are aLi.length!
}
}
}
複製代碼
點擊後會一直彈出同一個值 aLi.length 而不是123。當點擊以前,循環已經結束,i值爲aLi.length。
利用閉包,建一個匿名函數,將每一個i存在內存中, onclick函數用的時候提取出外部匿名函數的i值。代碼以下:
var aLi = document.getElementByClassName("test");
function showAllNum( aLi ){
for( var i =0,len = aLi.length ;i<len;i++ ){
(function(i){
aLi[i].onclick = function(){
alert( i );
}
})(i);
}
}
複製代碼
或者:
function showAllNum( aLi ){
for( var i =0,len = aLi.length ;i<len;i++ ){
aLi[i].onclick = (function(i){
return function(){
alert( i );
}
})(i);
}
}
複製代碼
解釋:實際上只是經過函數的賦值表式方式付給了標籤點擊事件,並無運行;當遍歷完後,i變成標籤組的長度,根據做用域的原理,向上找到for函數裏的i,因此點擊執行的時候都會彈出標籤組的長度。閉包可使變量長期駐紮在內存當中,咱們在綁定事件的時候讓它自執行一次,把每一次的變量存到內存中;點擊執行的時候就會彈出對應本做用域i的序號。
能夠減小全局變量的對象,防止全局變量過去龐大,致使難以維護
防止可修改變量,由於內部的變量外部是沒法訪問的,而且也不可修改的。安全
能夠讀取函數內部的變量,能夠把變量始終保存在內存中,不會在外層函數調用後被自動清除
變量長期駐紮在內存中
避免全局變量的污染
私有成員的存在
函數套函數
內部函數能夠直接使用外部函數的局部變量或參數
變量或參數不會被垃圾回收機制回收GC
常駐內存,會增大內存的使用量,使用不當會形成內存泄露,詳解:
一、因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。
二、閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象object
使用,把閉包看成它的公用方法Public Method
,把內部變量看成它的私有屬性private value
,這時必定要當心,不要隨便改變父函數內部變量的值。
IE9以前,JScript對象和COM對象使用不一樣的垃圾收集例程,那麼閉包會引發一些問題。
建立一個閉包,然後閉包有建立一個循環引用,那麼該元素將沒法銷燬。常見的就是dom獲取的元素或數組的屬性(或方法)再去調用本身屬性等。例如:
function handler(){
var ele = document.getElementById("ele");
ele.onclick = function(){
alert(ele.id);
}
}
複製代碼
閉包會引用包含函數的整個活動對象,便是閉包不直接引用ele,活動對象依然會對其保存一個引用,那麼設置null就能夠斷開保存的引用,釋放內存。代碼以下:
function handler(){
var ele = document.getElementById("ele");
var id = ele.id;
ele.onclick = function(){
alert(id);
}
ele = null;
}
複製代碼
由於閉包只有在被調用時才執行操做,因此它能夠被用來定義控制結構。多個函數可使用同一個環境,這使得他們能夠經過改變那個環境相互交流。