js 函數式編程 淺談
js 函數式編程
函數式的思想, 就是不斷地用已有函數, 來組合出新的函數。
函數式編程具有五個鮮明的特點:
1. 函數是第一等公民
指的是函數與其他資料類型一樣,處於平等地位
2. 只用運算式,不用語句
運算式(expression)是一個單純的運算過程,總是有傳回值;
語句(statement)是執行某種操作,沒有傳回值。
3. 沒有副作用
指的是函數內部與外部互動(最典型的情況,就是修改全域變數的值),
產生運算以外的其他結果。
4. 不修改狀態
變數往往用來儲存狀態(state)。不修改變數,意味著狀態不能儲存在變數中,
函數式編程使用參數儲存狀態
5. 引用透明
指的是函數的運行不依賴於外部變數或狀態,只依賴於輸入的參數,任何時候只要參數相同,
引用函數所得到的傳回值總是相同的
函數式編程的意義:
1. 代碼簡潔,開發快速
2. 接近自然語言,易於理解
3. 更方便的代碼管理
4. 易於並發編程
5. 代碼的熱升級
---------------------分割線------------------------
在 JavaScript 中,函數本身為一種特殊對象,屬於頂層對象,
不依賴於任何其他的對象而存在,因此可以將函數作為傳出 / 傳入參數,
可以儲存在變數中,可以做一切其他對象可以做的事情。
自調用函數(遞迴--自己調用自己)實際上是高階函數的一種形式。
函數式編程樣本:
// 階乘的一般實現function factorial(n) { if (n == 1) { return 1; } else { return factorial(n - 1) * n; }}// 階乘的函數式編程風格實現function mul(a, b){ return a*b; } function dec(x){ return x - 1; } function equal(a, b){ return a==b; } function factorial(n) { if (equal(n, 1)) { return 1; } else { return mul(n, factorial(dec(n))); }}---------------------分割線------------------------
函數柯裡化:
是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,
並且返回接受餘下的參數而且返回結果的新函數的技術。
我的理解就是需要反覆調用一個方法a(),它的參數中有一個參數在一定的狀況下的值不易改變,
每次調用都寫很多次未免太過麻煩,而且也體現不出來它描述和解決的一類問題,這種情況下,
為了避免這兩個問題,我們就將其中那個不容易改變的參數給固定下來,新產生一個函數來應對
這一類問題。
加法柯裡化的簡單實現:
//加法function adder(num) { return function(x) { return num + x; }}var add5 = adder(5);var add6 = adder(6);console.log(add5(1));// 6console.log(add6(1));// 7這裡的add5表示的函數可以解決基數為5的加法問題,只需要傳遞一個參數就可以了,
而不必像普通的加法函數那樣每次調用都必須添加兩個參數。
//計算m的n次方var powerOfN = function(n){ return function(m){ var res = 1; for(var i = 0; i < n; ++i){ res *= m; } return res; } ;};//按需產生var powerOf2 = powerOfN(2);var powerOf3 = powerOfN(3);//調用傳參console.log(powerOf2(3));console.log(powerOf3(2));柯裡化通用實現:
function curry(fn) { var args = [].slice.call(arguments, 1); return function() { var inargs = [].slice.call(arguments); console.log(args.concat(inargs)); return fn.apply(null, args.concat(inargs)); }}function curry(fn) { var args = [].slice.call(arguments, 1); return function() { var inargs = [].slice.call(arguments); console.log(args.concat(inargs)); return fn.apply(null, args.concat(inargs)); }}function add(num1, num2) { return num1 + num2;}var newAdd = curry(add, 5);console.log(newAdd(6));柯裡化將降低了函數使用的普遍性,增加了使用的特異性,使用柯裡化需要認真識別
其使用的情境,在符合要求的地方使用,不然會顯得囉嗦,降低代碼的可讀性。
---------------------分割線------------------------
高階函數
高階函數即為對函數的進一步抽象,就是以其它函數為輸入,或者返回一個函數為輸出的函數。
高階函數最常見的應用如 map(映射), reduce(規約), forEach(遍曆), filter(過濾)等,
它們都是以傳入不同的函數來以不同的方式運算元組元。
簡單應用:
function foo(f, g) { return function() { return f.call(null, g.apply(null, arguments)); //return f(g.apply(null, arguments)); 也是可以的 }}var sum = function(x, y) { return x + y;}var square = function(x) { return x * x;}var squareofsum = foo(square, sum);squareofsum(2, 3);// 25下面我們來看一下怎樣從過程式編程過渡到函數式編程的:
1>形式一
var sum = function(x, y) { return x + y;}var square = function(x) { return x * x;}function foo(){ return square( sum(2, 3) );}foo();// 252>形式二
var sum = function(x, y) { return x + y;}var square = function(x) { return x * x;}function foo(f, g){ return f( g(2, 3) );}foo(square, sum);// 253>形式三
var sum = function(x, y) { return x + y;}var square = function(x) { return x * x;}function foo(f, g){ return function(){ var num1 = arguments[0]; var num2 = arguments[1]; console.log(num1, num2);// 2 3 var temp = g(num1, num2); console.log(temp);// 5 return f(temp); };}var myfunc = foo(square, sum);myfunc(2, 3);// 254>形式四
var sum = function(x, y) { return x + y;}var square = function(x) { return x * x;}function foo(f, g){ return function(){ var temp = g.apply(null, arguments); return f(temp); };}var myfunc = foo(square, sum);myfunc(2, 3);// 25最後的形式四就是我們想要得到的效果。
---------------------分割線------------------------
其他樣本:
1>
function foo(fn, array, value){ var res = array.map(function(ele){ return fn.apply(null, [ele].concat(value)); }); return res;}function add(x, y) { return x + y;}function sub(x, y) { return x - y;}console.log( foo(add, [1, 2, 3], 3) );// [4, 5, 6]console.log( foo(sub, [1, 2, 3], 3) );// [-2, -1, 0]
2>
function multicast(fn) { return function (){ var pre = arguments[0]; var rest = arguments[1]; var ret = pre.map(function(ele) { return fn.apply(this, [ele].concat(rest)); }); return ret; }}function add(x, y) { return x + y;}var newAdd = multicast(add);console.log(newAdd([1,2,3],3));// [4, 5, 6]function sub(x, y) { return x - y;}var newSub = multicast(sub);console.log(newSub([1,2,3],3));// [-2, -1, 0]