ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[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)