在這篇文章裡,我們討論函數語言程式設計。
什麼是函數語言程式設計?根據百度百科的描述,“函數語言程式設計是種程式設計典範,它將電腦運算視為函式的計算。函式程式語言最重要的基礎是 λ 演算(lambda calculus)。而且λ演算的函式可以接受函式當作輸入(引數)和輸出(返回值)。和指令式程式設計相比,函數語言程式設計強調函式的計算比指令的執行重要。和過程化程式設計相比,函數語言程式設計裡,函式的計算可隨時呼叫。”
可以看出,在函數語言程式設計中,函式被看做是“一等公民”。JavaScript可以通過巧妙地函式組合來構建抽象,通過內嵌函式的方式,在軟體開發的過程中,我們可以把更多的精力放在“函式要做什麼”上,而不用太關心“函式如何做”的問題。
高階函式
可以操作其他函式的函式,被稱為高階函式。例如我們想對陣列的每一個元素做某種操作,那麼我們需要遍歷整理陣列,當操作發生改變時,我們還要重複編寫遍歷程式碼,利用高階函式,可以簡化這個過程。示例如下:
1 function forEach(array,func){ 2 for(var i = 0; i < array.length; i++){ 3 func(array[i]); 4 } 5 } 6 7 var a = ["a","b","c"]; 8 forEach(a,function(obj){print(obj);}); 9 10 forEach(a,function(obj){print(obj + 1);}); 11 12 13 //輸出結果 14 a 15 b 16 c 17 a1 18 b1 19 c1
forEach函式包含兩個引數,第一個引數是一個陣列,第二個引數是一個函式,在forEach函式體內,會遍歷陣列的每一個元素,然後針對每一個元素呼叫func函式。
在呼叫forEach函式時,我們針對第二個引數,使用了匿名函式,它接受一個引數,這個引數其實就是陣列中的元素。
從這個示例中,我們可以看到通過這種方式,可以明顯簡化對陣列的操作。
修改函式
我們可以通過高階函式很方便的修改已有函式的功能,示例如下:
1 function reverse(func){ 2 return function(value){ 3 return !func(value); 4 } 5 } 6 7 print(isNaN(NaN)); 8 var isNotNaN = reverse(isNaN); 9 print(isNotNaN(NaN)); 10 11 12 //輸出結果 13 true 14 false
reverse的作用是逆轉傳入函式的操作,在示例中,isNaN函式返回傳入引數是否是NaN。
規約函式
Reduce函式通過重複呼叫一個函式,將陣列轉換為單一的值。規約函式結構如下:
1 function reduce(combine,base,array){ 2 forEach(array, function(value){ 3 base=combine(base,value); 4 }); 5 return base; 6 }
Reduce函式中引數的順序是一個傳統,我們可以將第一個引數作為匿名函式的方式傳入。
下面是兩個使用Reduce函式的示例,分別計算陣列元素的和以及陣列中0的個數:
1 function countZeros(count,value){ 2 return value == 0 ?(count+1) : count; 3 } 4 5 function add(sum,value){ 6 return value+sum; 7 } 8 9 var a=[1,2,3,4,0]; 10 print(reduce(add,0,a)); 11 print(reduce(countZeros,0,a)); 12 13 14 //輸出結果 15 10 16 1
對映函式
Map函式會遍歷陣列,針對陣列的每個元素,呼叫指定的操作,然後將操作得出的值儲存到另外一個陣列中,並返回新陣列。
Map函式的結構如下:
1 function map(func,array){ 2 var result=[]; 3 forEach(array, function(value){ 4 result.push(func(value)); 5 }); 6 return result; 7 }
我們可以如下呼叫map方法:
1 var a=[1,2,3,4,0]; 2 3 print(map(function(value){ 4 return value*value; 5 }, a)); 6 7 //輸出結果 8 1,4,9,16,0
這個示例將陣列中的每個元素進行平方操作,然後輸出。
其他一些函式技巧
操作符函式
在JavaScript學習(1):基礎中,我們使用內嵌函式實現了四則運算,接下來我們試著另外一種實現方式:
1 var a=[1,2,3,4,0]; 2 var ops={"+":function(x,y){return x+y;}, 3 "-":function(x,y){return x-y;}, 4 "*":function(x,y){return x*y;}, 5 "/":function(x,y){return x/y;}, 6 }; 7 8 function operation(op, array){ 9 if (op in ops){ 10 return reduce(ops[op],0,array); 11 } 12 else{ 13 throw new Error("invalid operation."); 14 } 15 } 16 print(operation("+", a)); 17 print(operation("^", a)); 18 19 //輸出結果 20 10
物件中,屬性的值不僅僅可以是集合屬性,也可以是函式。上述示例使用這種方式對四則運算進行了封裝。然後呼叫reduce方法去計算陣列元素的和。
分佈應用
如果我們需要一個函式,但其中一個操作符的引數已經給定了,那應該如何處理?例如我們想對陣列中的每個元素都做加1操作,使用map的方式實現:
1 print(map(function(value){ 2 return value + 1; 3 },a));
在這裡,1是放在匿名函式中。我們還可以這樣做,使用分佈應用的方式在外函式和內嵌函式中分別儲存部分引數。
分佈應用的結構如下:
1 function partial(func){ 2 var knowArgs=arguments; 3 return function(){ 4 var realArgs=[]; 5 for(var i = 1; i < knowArgs.length; i++){ 6 realArgs.push(knowArgs[i]); 7 } 8 for(var i = 0; i < arguments.length; i++){ 9 realArgs.push(arguments[i]); 10 } 11 return func.apply(null, realArgs); 12 } 13 }
如果想要實現上面同樣的功能,程式碼如下:
1 print(map(partial(ops["+"], 1), a));
需要注意的是partial函式中在內嵌函式中如何將外函式和內嵌函式的引數進行整合,構成完整的引數列表。
以a=[1,2,3,4,0]為例,map函式會遍歷陣列中的每一個元素,當遍歷到2時,引數的變化過程:
1. 外函式引數:1) ops["+"]; 2) 1。
2. 內嵌函式引數: 2
3. 完整引數:1,2
4. func.apply(null, realArgs): ops["+"](1,2)
函式組合
函式組合的含義是在呼叫函式A的過程中,它使用函式B來計算返回結果。
就像這樣:
1 function compose(f1,f2){ 2 return function(){ 3 return f1(f2.apply(null,realArgs)); 4 } 5 }
上面示例中的isNotNaN也是這種情況。