函數式程式設計
- 前置知識
- js 基礎
- js面向對象
函數式程式設計含義
- 函數式程式設計是一種強調以函數使用為主的軟體開發風格 ,也是一種範式。
- 某些函數式程式設計語言Haskell、lisp、Scheme等。
js中函數式程式設計
-
數學中的函數
f(x) = y;
- js中的函數
let factor = 3; let totalNum = num=>factor*num; console.log( totalNum(3) );複制代碼
- 數學中的函數表達式相同的輸入,輸出是不變的,這就是函數程式設計的一個特性。将上面的js修改為符合函數式程式設計的規範如下。
let totalNum = (num,factor)=>factor*num; console.log( totalNum(3,3) );複制代碼
- js是多範式程式設計語言,但是函數作為一等公民,函數式程式設計具有天然優勢。
函數式程式設計中涉及到的概念
純函數
- 函數式程式設計基于純函數
let double = value=>value*2;複制代碼
- 純函數是對給定的輸入返還相同輸出的函數;例如
- 純函數意義
- 不依賴外部環境計算,不會産生副作用,提高函數的複用性。
test('double(2) 等于 4', () => { expect(double(2)).toBe(4); })複制代碼
- 純函數可以産生可測試的代碼
- 可讀性更強 ,js函數不管是否是純函數 都會有一個語義化的名稱,更便于閱讀。
- 可以組裝成複雜任務的可能性。符合子產品化概念及單一職責原則。
- 不依賴外部環境計算,不會産生副作用,提高函數的複用性。
高階函數
- 高階函數定義
- 高階函數:将函數作為參數或傳回函數的函數稱為高階函數(Higher-Order Function)。
- 高階函數的抽象
- 指令式循環(注重“如何”做,注重過程);
let arr = [1,2,3];for(let i=0;i<arr.length;i++){console.log(arr[i]); }複制代碼
- 通過高階函數抽象過程,聲明式程式設計(注重做“什麼”,注重結果);
上面通過高階函數 “forEach”來抽象循環"如何"做的邏輯,直接關注 做"什麼"const forEach = function(arr,fn){for(let i=0;i<arr.length;i++){ fn(arr[i]); } }let arr = [1,2,3]; forEach(arr,(item)=>{console.log(item); })複制代碼
- 一般高階函數用于抽象通用問題,簡而言之,高階函數就是定義抽象。
- 指令式循環(注重“如何”做,注重過程);
- 高階函數的緩存特性
- once 高階函數
- 利用js函數的閉包
const once = (fn)=>{let done = false;return function(){if(!done){ fn.apply(this,fn); }else{console.log("this fn is already execute"); } done = true; } } function test(){console.log("test..."); }let myfn = once(test); myfn(); myfn();複制代碼
柯裡化
- 什麼是柯裡化?
- 如下二進制函數
let fn = (x,y)=>x+y;複制代碼
- 柯裡化函數
const curry = function(fn){return function(x){return function(y){return fn(x,y); } } }let myfn = curry(fn);console.log( myfn(1)(2) );複制代碼
- 柯裡化是把一個多參數函數轉化成一個嵌套的一進制函數的過程;
- 如下二進制函數
- 多參數函數柯裡化
// 多參數柯裡化;const curry = function(fn){return function curriedFn(...args){if(args.length<fn.length){return function(){return curriedFn(...args.concat([...arguments])); } } return fn(...args); } }const fn = (x,y,z,a)=>x+y+z+a;const myfn = curry(fn);// console.log(myfn(1)(2));console.log(myfn(1)(2)(3)(1));複制代碼
- 柯裡化意義
- 讓純函數更”純“,每次接受一個參數,松散解耦
- 某些語言及特定環境下隻能接受一個參數
組合(composition)和管道(pipe)
組合(composition)
- 組合函數:無需建立新的函數,通過基礎函數解決眼前問題。
- 兩個個函數組合(緩存兩個函數,傳回新的函數,執行的時候按建立時的從右往左順序執行)
- 多函數組合
const compose = (...fns)=>val=>fns.reverse().reduce((acc,fn)=>fn(acc),val);複制代碼
let str = "Mamba Out,Mamba Never Out";const myfn = compose(oddOrEven,countFn,wordNum);console.log(myfn(str));複制代碼
- 擷取所有的單詞
const wordNum = str=>str.match(/[\w\-]+/g);let str = "Mamba Out,Mamba Never Out";console.log(wordNum(str));複制代碼
- 統計長度
const countFn = arr=>arr.length;複制代碼
- 判斷奇偶
const oddOrEven = num=>num%2===0?"偶數":"奇數";複制代碼
- 組合函數使用:找到單詞統計長度最後判斷奇偶數
-
compose組合
可以封裝組合函數來實作函數執行
function afn(a){return a*2; }function bfn(b){return b*3; }const compose = (a,b)=>c=>a(b(c));let myfn = compose(afn,bfn);console.log( myfn(2));複制代碼
管道(pipe)
compose 執行是從右到左,pipe是從左至右的執行。函數如下:
const pipe = (...fns)=>val=>fns.reduce((acc,fn)=>fn(acc),val);複制代碼
管道、組合 取舍 :管道及組合最大差別在于執行順序的不同,資料流向不同,達到目的是類似的。是以無優 劣之分,保持團隊風格統一就好了。
組合及管道的意義 把很多小函數組合起來完成更複雜的邏輯。