首先什麼是匿名函數?
匿名函數就是沒有名字的函數。
為什麼要有沒有名字的函數,而不給每個函數都起名字?
有些功能在某個範圍內只用一次而且很簡單,沒必要取個名字(當然取名字也可以),但是增加了代碼冗餘,因為這些取名字的工作都是在聲明函數,聲明函數是個苦力活,因為你一直在敲那些重複的function後面跟函數名,同時還要注意命名還不能跟已有函數重名,否則會覆蓋。最重要的是減少了代碼量卻實現了相同的功能,維護的時候更方便。
function functionName(arg0, arg1, arg2) {
//function body
}
var functionName = function(arg0, arg1, arg2) {
//function body
};
前者是聲明函數,在運行之前就會載入到相應的上下文(不論變數還是函數,瀏覽器都是先運行聲明產生類似先行編譯的效果),後者是把函數作為一個運算式賦值給一個變數,只有當啟動並執行時候才載入。
什麼是閉包?
閉包就是一個函數,這個函數能訪問其他函數的範圍內的變數。根據範圍鏈的規則要實現這個功能基本上只能靠嵌套函數實現,並且這個閉包函數是作為父函數的傳回值返回,而且這個閉包函數通常是個匿名函數。
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
此例中內部的匿名函數訪問了父函數的propertyName變數,而且當匿名函數作為createComparisonFunction的傳回值return了之後,這個匿名函數仍然能訪問這個propertyName。為什麼呢?
當一個函數被調用時,一個處於執行的上下文環境被建立,這個上下文環境建立了一個範圍鏈並且指派給了內部屬性(Scope),同時一個使用中的物件被初始化,這個使用中的物件擁有this,arguments和其他顯示命名的參數。然後Scope鏈中指向的第一個對象便是這個使用中的物件,緊接著,父函數也建立出了一個使用中的物件,類似的過程,Scope中指向的第2個對象是這個父函數的使用中的物件,這個過程一直持續向上遞迴,直到最後的父函數是當前的window對象或者說Scope鏈的最頂層指向的當前的全域上下文即window對象
當匿名函數作為傳回值從createComparisonFunction返回時,該匿名函數的範圍鏈才初始化出了3層對象(),同時createComparisonFunction的使用中的物件不能銷毀因為當createComparisonFunction傳回值時,內部的匿名函數的範圍鏈仍然有createComparisonFunction的使用中的物件,銷毀的只是createComparisonFunction函數的範圍鏈,createComparisonFunction函數的使用中的物件仍然駐留記憶體中直到匿名函數銷毀
//create function
var compareNames = createComparisonFunction(“name”);
//call function
var result = compareNames({ name: “Nicholas” }, { name: “Greg”});
//dereference function - memory can now be reclaimed
compareNames = null;
設定compareNames=null使匿名函數沒有被賦值給任何變數,因此可以被記憶體回收。
閉包有一個值得注意的特點是:閉包訪問的父函數的變數,這個變數總是所有操作後最後一次的值
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
此例中,10個匿名函數在各自的範圍鏈中都擁有createFunctions的使用中的物件,並且它們都指向了相同的變數i,當createFunctions結束執行時,i的值已經是10了。
如果要避免這個問題,可以做如下修改
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
因為函數是值傳遞,因此當i作為匿名函數參數傳遞給num時,只是匿名函數內部會產生一個i的副本,此時匿名函數內部沒有其他指向i的引用。
匿名函數中的this
var name = “The Window”;
var object = {
name : “My Object”,
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //”The Window”
匿名函數被認為是定義在全域上下文window中的,因此this總是指向window,從前面的圖中也可以知道。
如果要讓匿名函數訪問父函數的name,則應修改為:
var name = “The Window”;
var object = {
name : “My Object”,
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //”My Object”
匿名函數總是擁有父函數的“使用中的物件”
閉包的一個缺點是記憶體流失問題:
function assignHandler(){
var element = document.getElementById(“someElement”);
element.onclick = function(){
alert(element.id);
};
}
此例中匿名函數作為element元素的onclick事件的處理函數,因此父函數和匿名函數產生了一個循環參考(匿名函數擁有父函數的使用中的物件),只要匿名函數存在,對element對象的引用始終存在,記憶體也不會被回收。
如要避免此問題,做如下修改
function assignHandler(){
var element = document.getElementById(“someElement”);
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
此例中id作為element的id的副本被匿名函數引用,匿名函數中並沒有直接引用父函數的變數,但是匿名函數仍然擁有父函數的使用中的物件,為了消除這種引用,設定element為null,因而消除了對id為someElement的dom對象的引用