JavaScript 函数式编程
什么是函数式编程
JS 支持 函数式编程 ,因为函数是一等公民。函数也是数据,可以像变量一样保存、读取或在应用中流动。JS 的一些新的语法,比如箭头函数、Promise、展开语法等,使得函数式编程更方便了。
函数式编程是 声明式编程 的一部分,该编程风格有个特点:重点描述该做什么,而不管怎么做。相比之下,命令式编程 只关注如何使用代码得到结果。
采用声明式编程风格的代码更易于理解,因为代码本身就说明了在做什么:
const Welcome = () => (
<div id="welcome">
<h1>Hello World</h1>
</div>
)
ReactDom.render(<Welcome />, document.getElementById("target"))
这里用 React 使用作为例子,React 是声明式的。
Welcome
组件描述了如何渲染 DOM ,而ReactDOM.render()
则可以清晰地看出在给 id 为 "target" 渲染组件。
函数式编程有一些核心概念:不可变性、纯函数、数据转换、高阶函数、递归等。
核心概念
不可变性
不可变性是指不直接改变原来的数据,而是创建一个新的副本进行操作。这样可以保证原数据的完整。
比如 Array 的 concat()
方法就体现了不可变性:它不在原数组拼接,而是返回了一个新数组。
纯函数
纯函数应当是数学计算的模拟,对于相同的参数应当始终返回一致的结果。
纯函数应当没有副作用,不会对函数外变量、状态进行改变。纯函数应当把参数视为不可变数据。
因为纯函数不改变外部环境,所以易于测试。
数据转换
数据转换是 不可变性 的实践。
常见的有数组的迭代方法:map()
、reduce()
等,这些方法会返回一个处理过的数组副本。
高阶函数
高阶函数指处理其他函数的函数。应当至少满足其一:
接受其他函数作为参数;
返回一个函数。
比如 Array 的 map()
、filter()
等迭代方法就是满足第一个条件。而下面这个例子两个条件均满足:
// 返回一个计算 `f(g(x))` 的函数
function compose(f, g) {
return function(...args) => {
f.call(this, g.apply(this.args))
}
}
函数 柯里化 也满足第二个条件。柯里化要求将多参数的调用转为一连串单参数调用:
f(x, y, z) ---currying--> f(x)(y)(z)
柯里化要求函数应当具有固定数量的参数,而非可变。
递归
递归是指会调用自身的函数。一些会使用循环的问题往往可以转为递归函数:
// 斐波那契数列
const fibonacci = (n) => {
if (n <= 1) {
return n >= 0 ? n : undefined
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
Array(10).fill().map((_, i) => fibonacci(i)) // => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
树形数据结构适合用递归搜索元素。
参考
Functional programming - Wikipedia