函数式编程(简称FP):
是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)
可以理解为,以函数作为主要载体的编程方式,用函数去拆解、抽象一般的表达式
-
特性
-
函数是一等公民。:函数可以跟其他变量一样,可以作为其他函数的输入输出。
-
纯函数:一个函数的输出不受外部环境影响,同时也不影响外部环境时
-
引用透明 :同样的输入,必定是同样的输出。函数内部不依赖外部状态,如一些全局变量。
-
惰性计算 :一个表达式绑定的变量,不是声明的时候就计算出来,而是真正用到它的时候才去计算
-
常见的函数式编程模型
-
闭包
创造条件是:
- 存在内、外两层函数
- 内层函数对外层函数的局部变量进行了引用
// 简单的缓存工具
// 匿名函数创造了一个闭包
const cache = (function () {
const store = {};
return {
get(key) {
return store[key];
},
set(key, val) {
store[key] = val;
}
}
}());
cache.set('a', 1);
cache.get('a'); // 1
2.柯里化
给定一个函数的部分参数,生成一个接受其他参数的新函数
简单来讲就是部分应用, 也就是说 只传递函数的一部分参数来调用它,让它返回一个函数去处理剩下的参数。
// 柯里化之前
function add(x, y) {
return x + y;
}
add(1, 2) // 3
// 柯里化之后
function addX(y) {
return function (x) {
return x + y;
};
}
addX(2)(1) // 3
传入的参数个数是不固定的
function add(){
var args = [].slice.call(arguments);
var fn = function(){
var newArgs = args.concat([].slice.call(arguments));
return add.apply(null,newArgs);
}
fn.toString = function(){
return args.reduce(function(a, b) {
return a + b;
})
}
return fn ;
}
add(1)(2,3) //6
add(1)(2)(3)(4)(5) //15
这里就需要使用函数的toString来完成。
当我们返回函数的时候,会调用函数的toString来完成隐式转换。
3.组合
组合,顾名思义,也就是把多个函数组合起来变成一个函数。为了解决函数嵌套过深,包菜代码:h(g(f(x))),我们需要用到“函数组合”,让多个函数像拼积木一样。
const compose = (fn1, fn2) => args => fn1(fn2(args));
const toUpperCase = value => value.toUpperCase();
const addSuffix = value => `${value} is good!`;
const format = compose(toUpperCase, addSuffix);
format('apple'); // "APPLE IS GOOD!"
//fn2 先执行,然后将返回值作为 fn1 的参数,所以 compose 里面的方法是从右向左执行的
4.高阶函数
接受或者返回一个函数的函数称为高阶函数
当情况变得更加复杂时,表达式的写法会遇到几个问题:
- 表意不明显,逐渐变得难以维护
- 复用性差,会产生更多的代码量
- 会产生很多中间变量
在 JavaScript 中见到许多原生的高阶函数,例如 Array.map , Array.reduce , Array.filter
例如:
map 作为一个高阶函数,他接受一个函数参数作为映射的逻辑
// 数组中每一项加一,组成一个新数组
// 一般写法
const arr = [1, 2, 3];
const rs = [];
for (const n of arr) {
rs.push(++n);
}
console.log(rs)
// map改写
const arr = [1, 2, 3];
const rs = arr.map(n => ++n);
-
声明式与命令式代码
//命令式 let companies = [{name:"ellen",age:11},{name:"ant",age:23}]; let names = []; for (var i = 0; i < companies.length; i++) { names.push(companies[i].name) } //声明式 let names = companies.map(c => c.name);
-
优点
- 可复用性更高
- 可维护性更好
- 作用域局限,副作用少
- 更方便的代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试和除错,以及模块化组合。
-
总结
面向对象对数据进行抽象,将行为以对象方法的方式封装到数据实体内部,从而降低系统的耦合度。而函数式编程,选择对过程进行抽象,将数据以输入输出流的方式封装进过程内部,从而也降低系统的耦合度。函数对于外部状态的依赖是造成系统复杂性大大提高的主要原因,函数式编程能拓展我们的思维,不局限在过程式的编程代码。