JavaScript語言從一誕生,就具有函數(shù)式編程的烙印。它將函數(shù)作為一種獨立的數(shù)據(jù)類型篙悯,與其他數(shù)據(jù)類型處于完全平等的地位单匣。在JavaScript語言中夕凝,你可以采用面向?qū)ο缶幊蹋部梢圆捎煤瘮?shù)式編程户秤。有人甚至說码秉,JavaScript是有史以來第一種被大規(guī)模采用的函數(shù)式編程語言。
ES6的種種新增功能鸡号,使得函數(shù)式編程變得更方便转砖、更強大。本章介紹ES6如何進行函數(shù)式編程鲸伴。
柯里化
柯里化(currying)指的是將一個多參數(shù)的函數(shù)拆分成一系列函數(shù)府蔗,每個拆分后的函數(shù)都只接受一個參數(shù)(unary)。
function add (a, b) {
return a + b;
}
add(1, 1) // 2
上面代碼中汞窗,函數(shù)add
接受兩個參數(shù)a
和b
姓赤。
柯里化就是將上面的函數(shù)拆分成兩個函數(shù),每個函數(shù)都只接受一個參數(shù)仲吏。
function add (a) {
return function (b) {
return a + b;
}
}
// 或者采用箭頭函數(shù)寫法
const add = x => y => x + y;
const f = add(1);
f(1) // 2
上面代碼中不铆,函數(shù)add
只接受一個參數(shù)a
蝌焚,返回一個函數(shù)f
。函數(shù)f
也只接受一個參數(shù)b
誓斥。
函數(shù)合成
函數(shù)合成(function composition)指的是只洒,將多個函數(shù)合成一個函數(shù)。
const compose = f => g => x => f(g(x));
const f = compose (x => x * 4) (x => x + 3);
f(2) // 20
上面代碼中劳坑,compose
就是一個函數(shù)合成器毕谴,用于將兩個函數(shù)合成一個函數(shù)。
可以發(fā)現(xiàn)泡垃,柯里化與函數(shù)合成有著密切的聯(lián)系析珊。前者用于將一個函數(shù)拆成多個函數(shù),后者用于將多個函數(shù)合并成一個函數(shù)蔑穴。
參數(shù)倒置
參數(shù)倒置(flip)指的是改變函數(shù)前兩個參數(shù)的順序忠寻。
var divide = (a, b) => a / b;
var flip = f.flip(divide);
flip(10, 5) // 0.5
flip(1, 10) // 10
var three = (a, b, c) => [a, b, c];
var flip = f.flip(three);
flip(1, 2, 3); // => [2, 1, 3]
上面代碼中,如果按照正常的參數(shù)順序存和,10除以5等于2奕剃。但是,參數(shù)倒置以后得到的新函數(shù)捐腿,結(jié)果就是5除以10纵朋,結(jié)果得到0.5。如果原函數(shù)有3個參數(shù)茄袖,則只顛倒前兩個參數(shù)的位置操软。
參數(shù)倒置的代碼非常簡單。
let f = {};
f.flip =
fn =>
(a, b, ...args) => fn(b, a, ...args.reverse());
執(zhí)行邊界
執(zhí)行邊界(until)指的是函數(shù)執(zhí)行到滿足條件為止宪祥。
let condition = x => x > 100;
let inc = x => x + 1;
let until = f.until(condition, inc);
until(0) // 101
condition = x => x === 5;
until = f.until(condition, inc);
until(3) // 5
上面代碼中聂薪,第一段的條件是執(zhí)行到x
大于100為止,所以x
初值為0時蝗羊,會一直執(zhí)行到101藏澳。第二段的條件是執(zhí)行到等于5為止,所以x
最后的值是5耀找。
執(zhí)行邊界的實現(xiàn)如下翔悠。
let f = {};
f.until = (condition, f) =>
(...args) => {
var r = f.apply(null, args);
return condition(r) ? r : f.until(condition, f)(r);
};
上面代碼的關(guān)鍵就是,如果滿足條件就返回結(jié)果野芒,否則不斷遞歸執(zhí)行蓄愁。
隊列操作
隊列(list)操作包括以下幾種。
-
head
: 取出隊列的第一個非空成員狞悲。 -
last
: 取出有限隊列的最后一個非空成員涝登。 -
tail
: 取出除了“隊列頭”以外的其他非空成員。 -
init
: 取出除了“隊列尾”以外的其他非空成員效诅。
下面是例子胀滚。
f.head(5, 27, 3, 1) // 5
f.last(5, 27, 3, 1) // 1
f.tail(5, 27, 3, 1) // [27, 3, 1]
f.init(5, 27, 3, 1) // [5, 27, 3]
這些方法的實現(xiàn)如下。
let f = {};
f.head = (...xs) => xs[0];
f.last = (...xs) => xs.slice(-1);
f.tail = (...xs) => Array.prototype.slice.call(xs, 1);
f.init = (...xs) => xs.slice(0, -1);
合并操作
合并操作分為concat
和concatMap
兩種乱投。前者就是將多個數(shù)組合成一個咽笼,后者則是先處理一下參數(shù),然后再將處理結(jié)果合成一個數(shù)組戚炫。
f.concat([5], [27], [3]) // [5, 27, 3]
f.concatMap(x => 'hi ' + x, 1, [[2]], 3) // ['hi 1', 'hi 2', 'hi 3']
這兩種方法的實現(xiàn)代碼如下剑刑。
let f = {};
f.concat =
(...xs) => xs.reduce((a, b) => a.concat(b));
f.concatMap =
(f, ...xs) => f.concat(xs.map(f));
配對操作
配對操作分為zip
和zipWith
兩種方法。zip
操作將兩個隊列的成員双肤,一一配對施掏,合成一個新的隊列。如果兩個隊列不等長茅糜,較長的那個隊列多出來的成員七芭,會被忽略。zipWith
操作的第一個參數(shù)是一個函數(shù)蔑赘,然后會將后面的隊列成員一一配對狸驳,輸入該函數(shù),返回值就組成一個新的隊列缩赛。
下面是例子耙箍。
let a = [0, 1, 2];
let b = [3, 4, 5];
let c = [6, 7, 8];
f.zip(a, b) // [[0, 3], [1, 4], [2, 5]]
f.zipWith((a, b) => a + b, a, b, c) // [9, 12, 15]
上面代碼中,zipWith
方法的第一個參數(shù)是一個求和函數(shù)酥馍,它將后面三個隊列的成員辩昆,一一配對進行相加。
這兩個方法的實現(xiàn)如下旨袒。
let f = {};
f.zip = (...xs) => {
let r = [];
let nple = [];
let length = Math.min.apply(null, xs.map(x => x.length));
for (var i = 0; i < length; i++) {
xs.forEach(
x => nple.push(x[i])
);
r.push(nple);
nple = [];
}
return r;
};
f.zipWith = (op, ...xs) =>
f.zip.apply(null, xs).map(
(x) => x.reduce(op)
);
參考鏈接
- Mateo Gianolio, Haskell in ES6: Part 1
函數(shù)式編程--轉(zhuǎn)自:阮一峰《ECMAScript 6 入門》