[TOC]
# 函數式編程
函數式編程是一種典型的**聲明式編程**,與命令式編程相對立,它更看重程序的執行目標而不是執行過程。
函數式編程的一大特點是,函數是“一等公民”,意味著函數優先,提倡使用函數組合
我們先實現以下函數:
```js
const add = (x, y) => x + y;
const multiply= (x, y) = > x * y;
const subtract = (x, y) => x - y;
```
上述求值過程在函數式編程的理念下,就可以通過連接函數得到:
```js
let result= subtract(multiply(add(l,2), 3), 4);
```
我們看到,函數可以與其他數據一樣,作為參數傳遞,或作為返回值返回,這就是函數是
“一等公民”的體現。
## 純函數
Redux 中`reducer`函數 都需要是純函數 ,它的處理便是根據 `action` 參數產生一棵新的頁面狀態數據樹。這其實“暗合”(實際上是主動接受的)了函數式編程當中純函數的概念。
純函數代表這樣一類函數:
* 對于指定輸出,返回指定結果。
* 不存在副作用。
也就是說,**純函數的返回值只依賴其參數**。比如:
```js
//這是一個純函數
const addByOne = x => x + 1;
```
同時,在純函數內不能存在任何副作用,包括但不限于:
* 調用系統 I/O 的 API, `Date.now()` 或者 `Math.random()` 等方法。
* 發送網絡請求。在函數體內修改外部變量的值。
* 使用 `console.log()` 輸出信息。
* 調用存在副作用的函數等。
因為這些操作都具有不確定性,是“不純”的。換句話說,**對于純函數,如果是同樣的參數,則一定能得到一致的返回結果。作為開發者,根據其輸入,是完全可以預測輸出的**。
## 不可變性和共享數據
共享和不可變性是函數式編程推崇的重要概念,也是其顯著特點。保證數據的不可變性,
好處在于:開發**更加簡單、可回溯、測試友好,以及減少了任何可能的副作用,從而減少了Bug的出現**。
**共享**是指一個變量、對象或者內存空間在多個共享的作用域中出現,或者一個對象的屬性在多個作用域范圍內被傳遞。
共享帶來的問題是:針對共享的數據,我們需要完全掌握其在所有作用域空間內的情況,以保證代碼的正確性。
在 Redux的 `reducer` 一次更新過程中,不應該直接更改原有對象或數組的值,因為它們是引用類型,直接更改其值是不被允許的。**這時就需要新創建一個新的對象或數組用來承載新的數據,以保證純函數的特性**。
### JavaScript中滿足不可變性操作的方法
```js
let item = {
id:0,
book:'Learn Redux1',
available:true,
note:13
}
let newItem = Object.keys(item).reduce((obj,key)=>{
//console.log("obj is:",obj);
if(key !=='note'){
return {...obj, [key]:item[key] }
}
return obj;
},{})
console.log(newItem);
// {id: 0, book: "Learn Redux1", available: true}
console.log(item);
// {id: 0, book: "Learn Redux1", available: true, note: 13}
```
這里使用了 `Object.keys`及 `reduce` 方法,對除note屬性以外的所有屬性進行累加拷貝。這是很典型的函數式操作
一個很好的選擇是使用類似于underscore的類庫,這種類庫都會對數據操作進行函數式封裝。另外,將原有數據完全深拷貝一份,然后再對副本進行操作也是一個可選的方案。
### 實用類庫
社區中活躍著很多不可變的數據操作類庫,如 Facebook 的 [immer](https://github.com/mweststrate/immer)、[immutable.js](http://facebook.github.io/immutable-js/) 、[mori.js](http://swannodette.github.io/mori/) 等。使用它們實現的深拷貝及數據結構,在注重性能的同時也提供了方便實用的API。
但是引入這些第三方類庫也增加了一定的復雜度和學習成本。尤其是在同一個應用中,原生JavaScript數組、對象和這些類庫帶來的新的數據結構混雜,往往也存在著一定程度的淚亂。
如果開發者覺得使用JavaScript的 `slice`、`filter`、`map`、`reduce` 等函數式API,再結合ESNext 新特性己經完全可以滿足開發需求,那么就沒必要再使用類似于 immutable.js 的類庫了。
在取舍之間,開發者需要根據自身的業務情況及團隊風格進行選擇。
# JS 的**共享和可變性**
JavaScript 中的對象一般是可變的(Mutable),因為使用了引用賦值,新的對象簡單的引用了原始對象,改變新的對象將影響到原始對象。雖然這樣做可以有效的利用內存,但當應用復雜后,這就造成了非常大的隱患。
太過于靈活多變在復雜數據的場景下也造成了它的不可控性,假設一個對象在多處用到,在某一處不小心修改了數據,其他地方很難預見到數據是如何改變的,針對這種問題的解決方法,一般就像剛才的例子,會想復制一個新對象,再在新對象上做修改,這無疑會造成更多的性能問題以及內存浪費。
為了解決這個問題,一般的做法是使用 **shallowCopy(淺拷貝)** 或 **deepCopy(深拷貝)** 來復制一個新對象,從而使得新對象與舊對象引用地址不同,再在新對象上做修改,這無疑會造成更多的性能問題以及內存浪費。
## 與 Object.freeze、const 區別
```
a=Object.freeze({a:1});
b=a;
b.a=10;
a.a還是1
```
`Object.assign` 及擴展運算符`...` 、`Object.freeze` 和 ES6 中新加入的 `const` 都可以達到防止對象被篡改的功能,但它們是 `shallowCopy` (淺拷貝) 的。對象層級一深就要特殊處理了。
Redux 借鑒了函數式編程的思想,采用了單向數據流理念。是排斥這種共享和可變性的。
# immutable.js
而Immutable Data 就是一旦創建,就不能再被更改的數據。對 Immutable 對象的任何修改或添加刪除操作都會返回一個新的 Immutable 對象。
**Immutable 實現的原理是 Persistent Data Structure(持久化數據結構),也就是使用舊數據創建新數據時,要保證舊數據同時可用且不變**。同時為了避免 deepCopy 把所有節點都復制一遍帶來的性能損耗。
Immutable 使用了 Structural Sharing(結構共享),即如果對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享:
```視頻注釋
`[JSConf] Anjana Vakil- Immutable data structures`:
~~~[youku]
XMzQ5NTQzMDc1Mg
~~~
`Immutable User Interfaces (Lee Byron)`:
~~~[youku]
XMTcyNjY5Mzk3Mg==
~~~
```
[immutable.js](http://facebook.github.io/immutable-js/) 和React同期出現。是一個完全獨立的庫,無論基于什么框架都可以用它。意義在于它彌補了Javascript沒有不可變數據結構的問題。
**不可變數據結構是函數式編程(Functional Programming )中必備的**。前端工程師被 OOP 洗腦太久了,組件根本上就是函數用法,FP的特點更適用于前端開發。
在React開發中,頻繁操作state對象或是store,配合immutableJS快、安全、方便。
## immutableJS 三大特性:
* Persistent data structure (持久化數據結構)
* structural sharing (結構共享)
只重新創建新的節點來更新這個結構樹樹。其他節點都被重用了。(依然可以保留了原來的舊數據)
* support lazy operation (惰性操作)
應用惰性操作的特性,會節省非常多的性能
## 時間旅行小菜一碟
Undo/Redo,Copy/Paste,甚至時間旅行這些功能做起來小菜一碟。因為每次數據都是不一樣的,只要把這些數據放到一個數組里儲存起來,想回退到哪里就拿出對應數據即可,很容易開發出撤銷重做這種功能。
# 擁抱函數式編程
**Immutable 本身就是函數式編程中的概念,純函數式編程比面向對象更適用于前端開發。因為只要輸入一致,輸出必然一致,這樣開發的組件更易于調試和組裝。**
像 [ClojureScript](https://clojurescript.org/),[Elm](http://elm-lang.org/) 等函數式編程語言中的數據類型天生都是 Immutable 的,這也是為什么 ClojureScript 基于 React 的框架 --- Om 性能比 React 還要好的原因。
# 推薦做法
這點是我們使用 Immutable.js 過程中遇到最大的問題。寫代碼要做思維上的轉變。
雖然 Immutable.js 盡量嘗試把 API 設計的與原生對象類似,有的時候還是很難區別到底是 Immutable 對象還是原生對象,容易混淆操作。
Immutable 中的 Map 和 List 雖對應原生 Object 和 Array,但操作非常不同,比如你要用 `map.get('key')` 而不是 `map.key`,`array.get(0)` 、`array[0]`。另外 Immutable 每次修改都會返回新對象,也很容易忘記賦值。
**當使用外部庫的時候,一般需要使用原生對象,也很容易忘記轉換。**
下面給出一些辦法來避免類似問題發生:
1. 使用 TypeScript 這類有靜態類型檢查的工具
2. 約定變量命名規則:如所有 Immutable 類型對象以 `$$` 開頭。
3. 使用 `Immutable.fromJS` 而不是 `Immutable.Map` 或 `Immutable.List` 來創建對象,這樣可以避免 Immutable 和原生對象間的混用。
# 萬物皆有兩面性…
請不要認為這篇文章的意思是“你應該經常使用Immutable.js”,準確的講,我只是告訴你用它的所有好處,以及為什么要使用它。
當我在寫代碼的時候,我首先會用普通的 JavaScript 對象和數組,當我使用 Immutable.js 時,我需要非常確定,比如一個對象包含 10,000 個屬性。**我幾乎從不使用 Immutable.js,因為大多數時候的對象都很小**。
# 參考
[從JS對象開始,談一談“不可變數據”和函數式編程](https://www.jianshu.com/p/89f1d4245b20)
《React狀態管理與同構實戰》
[Immutable 詳解及 React 中實踐](https://zhuanlan.zhihu.com/p/20295971)
[Immutable.js 可持久化數據結構以及結構分享](https://blog.csdn.net/vM199zkg3Y7150u5/article/details/78210311)
[React實戰-Reactjs中的如何通過不可變數據對象提高頁面性能](https://blog.csdn.net/loveu2000000/article/details/52761498)
[Immutable.js 以及在 react+redux 項目中的實踐](https://blog.csdn.net/sinat_17775997/article/details/73603797)