JavaScript - 函數(shù)式編程

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ù)ab。

柯里化就是將上面的函數(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);

合并操作

合并操作分為concatconcatMap兩種。前者就是將多個數(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));

配對操作

配對操作分為zipzipWith兩種方法。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)
  );

參考鏈接

函數(shù)式編程--轉(zhuǎn)自:阮一峰《ECMAScript 6 入門》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容