JS函數式編程 - 概念

最近在看Typescript,順便看了一些函數式編程,而後半個國慶假期就沒有了。作個筆記,分幾個部分寫吧。html

最開始接觸函數式編程的時候,第一個接觸的概念就是高階函數,和柯里化。咋一看,這不就是長期用來說做用域的demo嗎?我在平常也有用啊,有啥嗎?數據庫

其實呢,設計模式或則編程範式每每不在於技巧,而在於思想。函數式編程就是一種編程的範式,並不在於技巧多麼叼,而在於它的思想。其次纔是由設計思想才衍生出來的技巧,技巧每每而言是服務於思想的。因此我以爲最開始學習函數式編程最好先了解一些相關概念和思想會比較好。編程

函數是一等公民(first class)

若是理解直接看爲一等公民的好處好處。設計模式

其實說函數式一等公民的意思就是說函數和其餘「公民」具備相同的屬性。就像任何一種數據類型,它可以被存儲在數組中,可以做爲函數的參數,可以賦值給變量:數組

const hello = (name) => (`Hello ${name}!`)
const sayHello = hello; // 做爲變量 
const helloArray = [hello, sayHello]

上面的代碼沒有什麼意義,只是表達函數在JavaScript中是一等公民,和一個值同樣。緩存

函數是一等公民的好處

拿一個callback的例子來說,好比你用fetch發個請求時:dom

fetch('getPostLink')
  .then(res => renderPosts(res))
  .catch(err => handleError(error))

上面其實能夠直接傳遞一個函數做爲回調,加一層包裹其實沒有必要:函數式編程

fetch('getPostLink').then(renderPosts).catch(handleError)

多一層函數的包裹並無任何意義,徹底是多餘的代碼。再看一個例子:函數

const postController = {
  find(postId) { return Db.find(postId) },
  delete(postId) { return Db.delete(postId) },
  ...
}

上面的代碼其實就是聚合一些功能做爲一個對象,可是多加了一層的函數,也是沒有必要的,在閱讀的時候到會增長複雜度,其實postController.find === Db.find,因此徹底沒有再去包裹一層函數:工具

const postController = {
  find: Db.find,
  delete: Db.delete,
  ...
}

上面的代碼是否是更表意,然而若是js的函數不能像值同樣傳遞,上面的簡寫都是不可能的。上面的代碼其實還有一個好處,你不用去糾結如何命名在兩層函數之間的參數了。這種風格代碼是符合Pointfree的,咱們後面要介紹。另外,函數式編程是操做函數的,因此函數是一等公民也是函數式的基石,基本上若是js不支持這一項,函數式根本玩不轉。

純函數

讓我舉一個例子,你們在小學多學過一元一次方程吧:

f(x) = ax+b

這就是一個純函數,一個輸入而後返回一個輸出。全部的東西都是圍繞輸入的,一個輸入只可能返回一個輸出,而後對任何其餘沒在做用域中的變量沒有任何操做。

更書面的解釋:一個純函數一個輸入永遠都只有一個一樣的輸出,而後不會產生任何反作用。反作用是啥咱們一下子再說。

不純的函數

一般不純的函數分爲兩類,一種是會改變輸入的:

const numbers = [1, 2, 3]
// 純函數
numbers.slice(0, 3) // [1, 2, 3]
numbers.slice(1, 3) // [2, 3]
numbers.slice(0, 2) // [1, 2]
// 不純的函數
numbers.splice(0, 3) // [1, 2, 3]
numbers.splice(0, 3) // []

上面中在numbers這個數組上面的兩個方法,slice是純函數。而splice則不是純函數,它會改變輸入的數值。作了額外的事。

另一種是對函數之外的狀態有依賴的:

let endpointForYoung = 18
// 不純的函數
const checkYoungPeople = age => age <= endpointForYoung

// 純函數
const checkYoungPeople = age => {
  const endpointForYoung = 18
  return age <= endpointForYoung
}

像上面的函數,第一個就是不純的,他依賴的做用域以外的一個變量,一旦這個變量改變,這個函數返回的值就會跟着改變。

反作用

反作用就是在函數計算過程當中,對函數外的狀態進行更改或則與函數外狀態進行交互的行爲。首先反作用會致使函數不純,是程序有不可控的依賴,不便於管理。可是,反作用是不可消除的,在正常的編程活動中是必然伴隨着反作用的。因此在面對反作用時,問題不是如何消除反作用,而是如何管理反作用。這個,會在咱們講解範疇論相關概念的時候再深刻。

正常編程活動中會引入的反作用有這些:

  1. 文件讀寫操做
  2. 增刪改查數據庫
  3. http 請求
  4. 打印log
  5. 得到用戶輸入
  6. 獲取dom元素

固然不限於上面這幾種,還有不少行爲都帶有反作用。

純函數的好處

  1. 能緩存

純函數的每次輸入和輸出都是沒有狀態的,因此結果都同樣,可以被緩存在任何地方而不會形成錯誤。

  1. 可移植性,自文檔
// 不純函數
const signUp = (attrs) => {
  const user = saveUser(attrs);
  welcomeUser(user);
};

// 純函數
const signUp = (Db, Email, attrs) => () => {
  const user = saveUser(Db, attrs);
  welcomeUser(Email, user);
};

第二個signUp依賴是從上傳遞的,因此能直觀的看出saveUser須要DbwelcomeUser還須要Email。在不純的函數中你很難在調用的時候知道他的依賴,你須要查看代碼,才能搞清楚,「哦,原來還用了Db存了波數據啊。」

依賴做爲參數傳入,也很容易的在移植到其餘場景使用,畢竟函數只是功能,針對不一樣場景操做不一樣的數據。

  1. 易於測試

寫單測的時候,最麻煩的就是如何mock數據。一般有兩類數據最難mock,第一個是全局變量,好比document,另一類是import進來的依賴,對於這兩種,雖然在一些測試套件中有現成的工具庫去mock。可是,都是很詭異的方式。

而若是是函數式的話,你測的就是一個輸入一個輸出,沒有外部的影響,是很是容易測試的。

  1. 並行代碼

純函數都是沒有狀態的,那即便跑在多臺機器多個進程,每一個單元相互之間是沒有耦合關係的。

Pointfree

你們能夠看阮老師的這篇博客瞭解一下:http://www.ruanyifeng.com/blo...

我只扯一下Pointfree風格代碼的好處:

  1. 正如上面所說的中間變量沒有意義,不須要給變量命名
  2. 代碼更簡潔,精煉,不用將過多的狀態暴露給消費者

固然也有人認爲其將太多的狀態隱藏了,初讀代碼很難理解,只有看了具體函數實現功能才能知道真正的意圖,對於代碼的可讀性而言,很糟糕。

這裏有一處在hacker news上的討論Point-Free style: What is it good for?。另外這還有一篇具體使用場景的文章https://medium.freecodecamp.org/how-point-free-composition-will-make-you-a-better-functional-programmer-33dcb910303a。有興趣的小夥伴能夠本身看看。

添加一張我筆記未整理的腦圖。
圖片描述

OK,下一篇介紹一下函數組合和柯里化。

相關文章
相關標籤/搜索