现代的JS有许多概念或者说一些编程技巧,这些概念有来自于其他语言、一些设计模式或者js语言特有的概念等。下面就来说一下与函数式编程密切相关的两个概念:纯函数和副作用
副作用
在计算机科学中,函数副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。 — 维基百科
例如在javascript
的内置的一些函数是有副作用的:
[1, 2, 3].pop() // 每次执行pop函数,原数组都会减少一个元素 [1, 2, 3].splice(1, 1) // 会删除原数组里面的元素 ...
除了这些内置的函数会存在副作用,我们平时写的一些操作Dom
、修改登录信息和弹出一个弹窗等,这些例子都太多了。
无副作用 & 纯函数
- 函数与外界的只有唯一一个渠道进行沟通,通过传入参数和返回值进行沟通。
- 相同的传入参数永远只会有相同的返回值。
最简单的例子:
function sum(a, b) { return a + b }
这两个概念是相当简单的,我们接下来看一下,函数式的出现给JS
带来了什么,解决了什么样的问题,存在哪些问题。
带来了什么
纯函数函数式有一些优点:
- 可缓存性(Cacheable)
- 可移植性/自文档化(Portable / Self-Documenting)
- 可测试性(Testable)
- 合理性(Reasonable)
- 并行代码(Parallel Code)
单从这些有点来看,确实是一些软件工程上所提倡的几个观点,在足够灵活的情况下还能保证稳定性。想想一些场景,当你使用第三方库的函数,传入了你的比较重要的一个对象,结果被修改了,导致了整个程序的错了,关键是这样的问题还不好定位到。
let importantObj = { a: 1 } someFunc(a) console.log(importantObj ) // ??? 不知道被修改成什么样子了。。。埋下了坑
比如,React
、lodash
和Redux
等就巧妙应用了js
的函数式编程,使得暴露出来的接口十分稳定,接口结构清晰。但是这些还只是用到了函数式的方式和技巧,还并不是完全的函数范式,真正的函数范式的限制更加严格,但是我认为不能一味的去追求这些严格的理论,而是适合的才是最好的。其中的一些函数式编程模型,比如:闭包(Closure)、高阶函数、柯里化(Currying)和组合(Composing)等,让更多的框架借鉴和使用,函数式编程这些特性给前端带来了更多的活力。
解决了什么样的问题
函数式编程关心数据的映射,命令式编程关心解决问题的步骤。如今前端应用功能越来越多,用户交互越来越多,所有需要处理的用户数据也是越来越复杂,管理这些数据是现在大家比较关心的事情和寻求突破的事情。
函数范式来处理这些数据,就可以使得数据的每一次修改都可以变得可以追溯,可以很容易的定位到问题的所在。因为无副作用的特性,减少了影响原始数据噪音,使得每次修改数据都比较稳定。
存在哪些问题
相对于声明式和命令式,函数式的编程是比较不容易理解的,有些接口的用法有时候可能不好理解。下面我引用一下《重新思考 Redux》(rematch 作者 Shawn McKay 写的一篇干货软文)里面有些相关段落进行举例。
以下内容来着精读《重新思考 Redux》 博客,里面文章很有深度,推荐阅读。简化初始化redux 初始化代码涉及的概念比较多,比如 compose
thunk
等等,同时将 reducer
、initialState
、middlewares
这三个重要概念拆分成了函数方式调用,而不是更容易接受的配置方式:
const store = preloadedState => { return createStore( rootReducer, preloadedState, compose(applyMiddleware(thunk, api), DevTools.instrument()) ); };
如果换成配置方式,理解成本会降低不少:
const store = new Redux.Store({ instialState: {}, reducers: { count }, middlewares: [api, devTools] });
笔者注:redux 的初始化方式非常函数式,而下面的配置方式就更面向对象一些。相比之下,还是面向对象的方式更好理解,毕竟 store 是一个对象。instialState 也存在同样问题,相比显示申明,将 preloadedState 作为函数入参就比较抽象了,同时 redux 对初始 state 的赋值也比较隐蔽,createStore 时统一赋值比较别扭,因为 reducers 是分散的,如果在 reducers 中赋值,要利用 es 的默认参数特性,看起来更像业务思考,而不是 redux 提供的能力。
通过上面博客的,可以看到,函数式的一些代码理解起来并不是那么容易。
因为函数式的处理方式,每次都是产生一个新的数据,所有相对来说,需要的内存和占的资源是较高的。
总结
经过上面的分析,我们可以看到函数式的一些模型早已在如今的前端各种库实行开来,并且效果都还很不错,了解函数式的这个概念可谓是非常有必要的,但是是否一定需要函数式需要多多考虑。还是记住只有适合的技术。