在這篇文章裏,咱們討論函數式編程。html
什麼是函數式編程?根據百度百科的描述,「函數式編程是種編程典範,它將電腦運算視爲函數的計算。函數編程語言最重要的基礎是 λ 演算(lambda calculus)。並且λ演算的函數能夠接受函數看成輸入(參數)和輸出(返回值)。和指令式編程相比,函數式編程強調函數的計算比指令的執行重要。和過程化編程相比,函數式編程裏,函數的計算可隨時調用。」編程
能夠看出,在函數式編程中,函數被看作是「一等公民」。JavaScript能夠經過巧妙地函數組合來構建抽象,經過內嵌函數的方式,在軟件開發的過程當中,咱們能夠把更多的精力放在「函數要作什麼」上,而不用太關心「函數如何作」的問題。數組
能夠操做其餘函數的函數,被稱爲高階函數。例如咱們想對數組的每個元素作某種操做,那麼咱們須要遍歷整理數組,當操做發生改變時,咱們還要重複編寫遍歷代碼,利用高階函數,能夠簡化這個過程。示例以下:app
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。xml
Reduce函數經過重複調用一個函數,將數組轉換爲單一的值。規約函數結構以下:htm
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也是這種狀況。