简单说,"函数式编程"是一种 "编程范式"(programming paradigm),也就是如何编写程序的方法论。javascript
它属于 "结构化编程" 的一种,主要思想是把运算过程尽可能写成一系列嵌套的函数调用。举例来讲,如今有这样一个数学表达式:html
(5+6) - 1 * 3复制代码
传统的过程式编程,可能这样写:前端
var a = 5 + 6;
var b = 1 * 3;
var c = a - b;复制代码
函数式编程要求使用函数,咱们能够把运算定义成不一样的函数:java
const add = (a, b) => a + b;
const mul = (a, b) => a * b;
const sub = (a,b) => a - b;
sub(add(5,6), mul(1,3));复制代码
咱们把每一个运算包成一个个不一样的函数,而且根据这些函数组合出咱们要的结果,这就是最简单的函数式编程。typescript
函数为一等公民 (First Class)shell
所谓 "一等公民"(first class),指的是函数与其余数据类型同样,处于平等地位,能够赋值给其余变量,也能够做为参数,传入另外一个函数,或者做为其它函数的返回值。express
函数赋值给变量:编程
const greet = function(msg) { console.log(`Hello ${msg}`); }
greet('Semlinker'); // Output: 'Hello Semlinker'复制代码
函数做为参数:数组
const logger = function(msg) { console.log(`Hello ${msg}`); };
const greet = function(msg, print) { print(msg); };
greet('Semlinker', logger);复制代码
函数做为返回值:并发
const a = function(a) {
return function(b) {
return a + b;
};
};
const add5 = a(5);
add5(10); // Output: 15复制代码
只用表达式,不用语句
"表达式"(expression)是一个单纯的运算过程,老是有返回值;"语句"(statement)是执行某种操做,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,并且都有返回值。
缘由是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操做,因此就被排斥在外。
Pure Function
Pure Function (纯函数) 的特色:
所谓 "反作用")(side effect),是指函数内作了与自己运算无关的事,好比修改某个全局变量的值,或发送 HTTP 请求,甚至函数体内执行 console.log
都算是反作用。函数式编程强调函数不能有反作用,也就是函数要保持纯粹,只执行相关运算并返回值,没有其余额外的行为。
前端中常见的产生反作用的场景:
接下来咱们看一下纯函数与非纯函数的具体示例:
纯函数示例:
const double = (number) => number * 2;
double(5);复制代码
非纯函数示例:
Math.random(); // => 0.3384159509502669
Math.random(); // => 0.9498302571942787
Math.random(); // => 0.9860841663478281复制代码
不修改状态 - 利用参数保存状态
函数式编程只是返回新的值,不修改系统变量。所以,不修改变量,也是它的一个重要特色。
在其余类型的语言中,变量每每用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归,具体示例以下:
function findIndex(arr, predicate, start = 0) {
if (0 <= start && start < arr.length) {
if (predicate(arr[start])) {
return start;
}
return findIndex(arr, predicate, start+1);
}
}
findIndex(['a', 'b'], x => x === 'b'); // 查找数组中'b'的索引值复制代码
示例中的 findIndex 函数用于查找数组中某个元素的索引值,咱们经过 start 参数来保存当前的索引值,这就是利用参数保存状态。
引用透明
引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或 "状态",只依赖于输入的参数,任什么时候候只要参数相同,引用函数所获得的返回值老是相同的。
非引用透明的示例:
const FIVE = 5;
const addFive = (num) => num + FIVE;
addFive(10);复制代码
1.代码简洁,开发快速
函数式编程大量使用函数,减小了代码的重复,所以程序比较短,开发速度较快。
2.接近天然语言,易于理解,可读性高
函数式编程的自由度很高,能够写出很接近天然语言的代码。咱们能够经过一系列的函数,封装数据的处理过程,代码会变得很是简洁且可读性高,具体参考如下示例:
[1,2,3,4,5].map(x => x * 2).filter(x => x > 5).reduce((p,n) => p + n);复制代码
3.可维护性高、方便代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果一定相同。所以,每个函数均可以被看作独立单元,颇有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
4.易于"并发编程"
函数式编程不须要考虑"死锁"(deadlock),由于它不修改变量,因此根本不存在"锁"线程的问题。没必要担忧一个线程的数据,被另外一个线程修改,因此能够很放心地把工做分摊到多个线程,部署"并发编程"(concurrency)。
forEach
在 ES 5 版本以前,咱们只能经过 for 循环遍历数组:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
for (var i =0, len = heroes.length; i < len; i++) {
console.log(heroes[i]);
}复制代码
在 ES 5 版本以后,咱们可使用 forEach 方法,实现上面的功能:
forEach 方法签名:
array.forEach(callback[, thisArg])复制代码
参数说明:
以上示例 forEach 方法实现:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
heroes.forEach(name => console.log(name));复制代码
map
在 ES 5 版本以前,对于上面的示例,若是咱们想给每一个英雄的名字添加一个前缀,但不改变原来的数组,咱们能够这样实现:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var prefixedHeroes = [];
for (var i =0, len = heroes.length; i < len; i++) {
prefixedHeroes.push('Super_' + heroes[i]);
}复制代码
在 ES 5 版本以后,咱们可使用 map 方法,方便地实现上面的功能。
map 方法签名:
const new_array = arr.map(callback[, thisArg])复制代码
参数说明:
以上示例 map 方法实现:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var prefixedHeroes = heroes.map(name => 'Super_' + name);复制代码
filter
在 ES 5 版本以前,对于 heroes 数组,咱们想获取名字中包含 m
字母的英雄,咱们能够这样实现:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var filterHeroes = [];
for (var i =0, len = heroes.length; i < len; i++) {
if(/m/i.test(heroes[i])) {
filterHeroes.push(heroes[i]);
}
}复制代码
在 ES 5 版本以后,咱们可使用 filter 方法,方便地实现上面的功能。
filter 方法签名:
var new_array = arr.filter(callback[, thisArg])复制代码
参数说明:
以上示例 filter 方法实现:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var filterRe = /m/i;
var filterHeroes = heroes.filter(name => filterRe.test(name));复制代码