高階函數(higher order function),簡單說就是函數可以當作變數來用。因此就可以當作參數傳遞給函數,函數也以返回函數。
其實C語言可以透過函數指標做到類似的功能,但是javascript允許巢狀定義函數,這個在C語言很難做到。有興趣的話可以參考一個古老的問題,叫做funarg:
http://en.wikipedia.org/wiki/Funarg_problem
不多說了,直接來看有什麼好玩的東西吧。
函數當作參數
Javascript的陣列,允許你用自己的方法做排序,如果你在陣列裡面放的是Object,而又想排序時,就可以用自訂的排序函數來做排序。
許多語言針對陣列有很方便的功能,例如filter、each等,但是javascript沒有內建...不過其實加起來很容易:
<script>
Array.prototype.filter = function(f) {
if(typeof f == "function") {
var ret = [];
var i = 0;
for (; i<this.length; i++) {
if (f(this[i])) {
ret.push(this[i]);
}
}
return ret;
}
return this;
}
Array.prototype.each = function(f) {
if(typeof f == "function") {
var ret = [];
var i = 0;
for (; i<this.length; i++) {
ret.push(f(this[i]));
}
return ret;
}
return this;
}
var a = [1,3,5,7,9];
alert( a.filter(function(n){if (n>=5) return true;}).each(function(n){return n+1;}) );
</script>
像這樣,就可以為陣列物件加上一個filter方法及each方法;傳入一個過濾元素的函數,就可以用簡潔的語法過濾陣列元素;傳入一個處理陣列元素的函數,就可以處理每個陣列元素。
另外,從上例也可以看出來,這樣的做法就很像委託delegate,善用的話可以降低耦合,讓程式更有彈性。
另外一個常用到要傳入函數做為參數的場合,就是把傳入的函數當作一個callback,也有人用continuation passing style來稱呼,不過意思可能稍有不同。
最常用的,用jquery來舉例吧:
$(document).ready(function(){
$("#id").click(function(){alert('you clicked me.');});
});
ready跟click都是jqery把事件包裝起來的函數,實際的事件處理函數,就用參數的形式傳給他執行。
(關於Continuation Passing Style可以參考http://en.wikipedia.org/wiki/Continuation-passing_style)
函數返回函數
如果有接觸functional language,應該會聽過Currying這個名詞(這不是咖哩化,這不是咖哩化)。用這個方法可以把接收多個參數的函數轉換成只接收一個參數的函數,當中就是利用函數返回函數的方式來達成。
例如一個有三個參數的函數:
function calc(x, y, f) {
return f([x,y]);
}
alert(calc(3,4,function(a){return a[0]+a[1];}));
改成按照順序每次接收一個參數的函數:
function calc1(x) {
return function (y) {
return function (f) {
return f([x,y]);
};
};
}
alert(calc1(3)(4)(function(a){return a[0]+a[1];}));
這樣看不出威力,其實它的好處是可以這樣用:
var a = calc1(3);
//do something
var b = a(4);
//do something;
alert(b(function(a){return a[0]+a[1];}));
用這個方式,就可以延遲function的執行。
這些作法其實在各個Javascript Library或Framework都很常見,多看看這些Library或Framework的原始碼,對於活用Javascript會很有幫助。