1.函式的概念
javascript中最關鍵的概念:函式是第一類物件,或稱它們為一等公民。
複製程式碼
函式也可以被視為其他任意型別的javascript物件。javascript中函式擁有物件的所有能力,頁因此函式可被作為任意其他型別物件來對待。當我們說函式是第一類物件時就是說函式也能夠實現以下功能:
- 1.通過字面量建立,陣列項或其他物件的屬性。
- 2.賦值給變數,陣列或其他物件的而屬性。
- 3.作為函式的引數傳遞。
- 4.作為函式的返回值。
- 5.具有動態建立和分配的屬性。
物件能做的任何一件事,函式也能做。函式也是物件,唯一的特殊之處在於它是可呼叫的,即函式會被呼叫以便執行某項操作。
2.回撥函式
每當我們建立一個將在隨後呼叫的函式時,無論是在事件處理階段通過瀏覽器還是通過其他程式碼,我們都是在建立一個回撥(callback)。這個術語源自於這樣一個事實,即在執行過程中,我們建立的函式會在稍後的某個合適時間點“再回來呼叫”。
function useless(callback){
return callback();
}
複製程式碼
這個函式可能沒什麼用,但它反應了函式的一種能力,即將函式作為另一個函式的引數,隨後通過引數來呼叫------這個也就是上面所說的函式的第三個功能!
var text="Domo arigato!";
report("Before defining functions");
//函式定義,引數作為一個回撥函式,其函式體內會立即呼叫該回撥函式
function useless(callback){
report("In useless function");
return callback();
}
//簡單的函式定義,僅返回一個全域性變數
function getText(){
report("In getText function");
return text;
}
report("Before making all the calls");
//把getText作為回撥函式傳入上面的useless函式
assert(useless(getText)===text,
"The useless function works!"+text);
report("After the calls have been made!")
複製程式碼
javascript的重要特性之一是可以在表示式出現的任意位置建立函式,除此之外這種方式能使程式碼更緊湊和易於理解(把函式定義放在函式使用處附近)。當一個函式不會在程式碼的多處位置被呼叫時,該特性可以避免用非必需的名字汙染全域性命名環境。
document.body.addEventListener("mousemove",function(){
var second=document.getElementById("second");
addMessage(second,"Event:mousemove");
});
複製程式碼
上述同樣是一個回撥函式的例子,作為mousemove事件的事件處理器,當事件發生時,會被瀏覽器呼叫。
綜上:回撥函式是其程式碼會在隨後的某個合適時間點“回過來呼叫”的函式。
複製程式碼
3.儲存函式
某些例子中(例如,我們需要管理某個事件發生後需要呼叫的回撥函式集合),我們會儲存元素唯一的函式集合。當我們向這樣的集合中新增函式時,會面臨兩個問題:哪個函式對於這個集合來說是一個新函式,從而需要被加入到該集合中?又是哪一個函式已經存在於集合中,從而不需要再次加入到集合中?一般來說,管理回撥函式集合時,我們並不希望存在重複函式,否則一個事件會導致同一回撥函式被多次呼叫。
給函式附加一個屬性後,我們就可以引用該屬性。下面的例子就是通過該方法確保ninja函式僅被新增到函式中一次。
複製程式碼
var store={
nextId:1, //跟蹤下一個要被複制的函式
cache:{}, //使用一個物件作為快取,我們可以在其中儲存函式
add:function(){
if(!fn.id){ //僅當函式唯一時,將該函式加入快取
fn.id=this.nextId++;
this.cache(fn.id)=fn;
return true;
}
}
};
function ninja(){};
//測試上面程式碼按預期工作
assert(store.add(ninja),
"Function was safely added.");
assert(!store.add(ninja),
"But it was only added once.");
複製程式碼
另外一種有用的技巧是當使用函式屬性時,可以通過該屬性修改函式自身。這個技術可以用於記憶前一個計算得到的值,為之後計算節省時間。
4.自記憶函式
記憶化是一種構建函式的處理過程,能夠記住上次計算結果。在這個外殼中,當函式計算得到結果時就將該結果按照引數儲存起來。採用這種方式,如果另外一個呼叫也使用相同的引數,我們則可以直接返回上次儲存的結果而不是再計算一遍,這樣既避免重複又複雜的計算也可以顯著的提高效能。(記憶化特指函式的返回值)
下面的例子使用一個簡單的演算法計算素數,儘管這是一個複雜計算的簡單例子,但它經常被應用到大計算量的場景中。
function isPrime(val){
//建立快取
if(!isPrime,answers){
isPrime.answers={};
}
//檢查快取的值
if(isPrime.answers[val] !== undefined){
return isPrime.answers[val];
}
var prome = val !== 0 && val !==1; //1 is not a prime
for (var i = 2;i<val;i++){
if(val % i ===0){
prime = false;
break;
}
}
return isPrime.answer[val] = prime; //<--------儲存計算的值
}
//測試該函式是否正常工作
assert(isPrime(5),"5 is prime!");
assert(isPrime.answers[5],"The answer was cached!");
複製程式碼
這個方法有兩個優點:
- 1.由於函式呼叫時會尋找之前呼叫所得到的值,所以使用者最終會樂於看到所獲得的效能收益;
- 2.它幾乎是無縫地發生在後臺,終端使用者和頁面作者都不需要執行任何特殊請求,也不需要做任何額外初始化,就能順利進行工作。
當然這種方法並不是像玫瑰和提琴一樣完美,還是要權衡利弊,任何型別的快取都必然會為效能犧牲記憶體!
5.立即函式
函式表示式可以放在初看起來有些奇怪的位置上,例如通常認為是函式識別符號的位置。接下來仔細看看這個構造:
在javascript庫中會經常見到這幾種形式:
- 1.+function(){}();
- 2.-function(){}();
- 3.!function(){}();
- 4.~function(){}();
不同於用括號的方式區分函式表示式和函式宣告,這裡我們使用一元操作符+,-,!和~。這種做法也是用於向javascript引擎指明它處理的是表示式,而不是語句。