javascript函數語言程式設計

pengyaoli發表於2020-09-25

為什麼使用函數語言程式設計

1. React、vue3都支援函數語言程式設計

2. 函數語言程式設計可以拋棄this

3. 打包中可更好利用tree taking過濾沒有使用的程式碼

4. 方便單元測試,方便並行處理

5. 使程式碼更簡潔,更通用,擴充套件性更好

什麼是函數語言程式設計

函數語言程式設計(Functional Programming,FP)是程式設計正規化之一s,與物件導向程式設計並列

函數語言程式設計的思維方式:對運算過程對抽象

函數語言程式設計的函式是數學中的函式即對映關係,如y=sin(x)

相同輸入會得到相同輸出(純函式),如陣列的slice方法

函數語言程式設計是描述資料(函式)之間的對映

相關概念

1. 函式是一等公民

函式可儲存到變數中,可作為函式的引數,可作為返回值

2. 高階函式

概念:函式可作為引數傳遞給其他函式,函式可作為返回值
意義:可抽象具體的細節,而只關注輸入與輸出,高階函式可抽象通用問題,更具有擴充套件性
常用的高階函式:forEach, map, reduce, every, some, sort, filter, find等

3. 閉包

概念:函式和其周圍的狀態的引用捆綁在一起形成閉包,簡而言之,在裡層作用域中使用外層作用域擁有的變數,即形成閉包
本質:函式被放到呼叫棧執行後,會銷燬此函式,但當其中的變數被外部引用時,不會被銷燬和釋放,故里層函式可使用外層函式的變數
例子:es6的let定義變數會形成閉包,例如:
for (let i = 0; i < 10; i++) {
	setTimeout(() => {
		console.log(i);
	}, 10)
}
此時列印值為1-9,但是如果使用var定義或let定義放到for迴圈的上方,則不能形成閉包,列印值就為10

4. 純函式

概念:相同輸入會得到相同輸出,而且沒有任何副作用,所謂副作用就是不可預測的結果,比如函式中使用服務端返回的資料,全域性變數等
純函式類似於數學中的計算公式,比如y=sin(x), x值只要確定,那麼y的值就是定值
示例:陣列的slice是純函式,splice不是純函式
函數語言程式設計不會保留中間過程,只會有返回值,可以把返回值去當引數傳給其他函式
好處:
1. 可快取,因為相同輸入有相同輸出,所以可以把返回值快取下來,不用引數相同時,去多次執行
2. 方便自我除錯,對超出預期對結果可通過增加斷言函式去列印結果
3. 並行處理,因為純函式只依賴引數,而且不會修改全域性變數,所以就算是多執行緒或多處都呼叫,也無須擔心出現預期外的錯誤
副作用:會讓純函式變為不純,比如配置檔案,資料庫,全域性變數,dom
副作用會降低函式的通用性、擴充套件性、重用性,但副作用不可避免,比如dom互動等

5.柯里化

概念:當函式存在多個引數時,可呼叫函式時傳遞一部分引數,返回個新函式,再呼叫新函式時,傳遞剩下的引數,當引數全部傳入後,再執行此函式
例子:
var _ = require("lodash");
var getSums = (a, b, c) => {return a + b + c};
var getSumsByCurry = _.curry(getSums);
getSumsByCurry(1)(2)(3); // 等價於getSums(1,2,3);
手寫curry:
var curry = function (fn) {
    return function f(...args) {
        if (args.length >= fn.length ) {
            return fn(...args);
        }
        return function(...ab) {
            return f(...args, ...ab);
        }
    }
}
總結:柯里化可以得到快取某些公共引數的函式,讓函式的粒度更小,更靈活,可以把多個引數的函式變為只傳一個引數的函式,方便函式組合時使用

6.函式組合

函式組合可以把多個細粒度的函式組合成新的函式,如果一個函式需要其他函式的返回值作為引數,這時就可以把這些函式組合成一個新的函式
函式式組合可最大限度的去重新利用細粒度的函式,提高函式的重複利用
手寫函式組合,從右到左執行
function compose (...args) {
  return function (value) {
    return args.reverse().reduce(function (ret, fn) {
      return fn(ret)
    }, value)
  }
}
var fn = compose(a(), b());
fn(1) // 等價於a(b(1));

7.函子(functor)

概念:是一個特殊的容器,由物件實現,該物件必須有map方法,map方法可以對值進行處理
class Container {
  static of (value) {
    return new Container(value)
  }

  constructor (value) {
    this._value = value
  }

  map (fn) {
    return Container.of(fn(this._value))
  }
}
let r = Container.of(5)
          .map(x => x + 2)
          .map(x => x * x)

總結:
可處理函式對副作用,不在函式內呼叫副作用對函式,而是暴露出,比如對外暴露run方法,當執行run時,才處理某個傳入的函式
map方法會呼叫傳入對函式,返回值為處理過的新的函子
函子就是約定好有map特定作用函式的物件,把傳入的value封裝,當需要改變value時,通過map方法去實現
型別:
MayBe函子:相容非空
Either函子:類似if else
IO函子:value為函式,當有需要時,由呼叫方手動呼叫
Pointed函子:實現了of方法當函子
Monad函子:使巢狀函子時,把呼叫方式拍扁

相關文章