跳到主要内容

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

Pure function - Wikipedia

Higher-order function - Wikipedia

柯里化(Currying)- javascript.info