函数式编程
函数式编程是一种编程范式,强调函数的使用和函数之间的组合。
函数是一等公民
函数可以作为函数的参数,也可以作为返回值
纯函数
纯函数特性:
- 相同的输入值,总是返回相同的输出值
- 没有任何副作用
纯函数示例:
const add = (a, b) => a + b;
副作用是指函数对外部状态的影响,如修改全局变量、修改参数值、修改 DOM 等。
非纯函数示例:
let obj = { a: 1 };
function fn(p) {
p.a = 2;
}
fn(obj);
console.log(obj); // { a: 2 }
不可变性
数据一旦创建就不能被改变。任何修改数据的操作都会返回一个新的数据副本。
const arr = [1, 2, 3];
const newArr = [...arr, 4];
例子中使用扩展运算符创建新数组,不会改变原始数组。
高阶函数
高阶函数是指可以接收函数作为参数,或者返回函数的函数。
const map = (arr, fn) => arr.map(fn);
const double = x => x * 2;
const res = map([1, 2, 3], double); // [2, 4, 6]
函数组合
将多个函数组合成一个函数,前一个函数的输出作为后一个函数的输入。
const compose = (f, g) => x => f(g(x));
const add = x => x + 1;
const double = x => x * 2;
const res = compose(double, add)(1); // (2 + 1) * 2 = 6
函数柯里化
柯里化(Currying)是把一个多参数的函数,转变为一系列单参数函数的过程。
function f(x, y) {
return x + y;
}
f(1, 2);
function g(x) {
return function (y) {
return x + y;
};
}
g(1)(2);
示例 1:累加计数,实现 add(1)(2)(3)
、add(1, 2)(3)
、add(1)(2, 3)
、add(1, 2, 3)
的结果都是 6
ES5 实现:
function curry1(fn) {
return function curried() {
var args = Array.prototype.slice.call(arguments);
if (args.length < fn.length) {
return function () {
var args2 = Array.prototype.slice.call(arguments);
return curried.apply(null, args.concat(args2));
};
}
return fn.apply(null, args);
};
}
ES6 实现:
function curry(fn) {
return function curried(...args) {
if (args.length < fn.length) {
return function () {
return curried(...args.concat(Array.from(arguments)));
};
}
return fn(...args);
};
}
使用方式:
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6
curriedAdd(1, 2, 3); // 6
示例 2:实现不限层级的累加,如 add(1)(2)(3)
的结果是 6
初步实现:
function add(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
add(1)(2)(3);
这种方式只能实现有限的层级,并不能无限嵌套。下面使用闭包,并重写函数的 toString()
方法来实现任意数累加
function add(a) {
function sum(b) {
a = a + b;
return sum;
}
sum.toString = function () {
return a;
};
return sum;
}
const res = add(1)(2)(3).toString();
这里的调用链实际上是在执行多次 sum 函数,每次将参数累加到 a 上。最终的返回值是一个函数,该函数的 toString 方 法返回累加后的结果。
函数缓存
函数缓存,就是将函数运行过的结果进行临时缓存,用空间换时间。实现函数缓存主要依靠闭包、高阶函数、柯里化。
实现原理:把参数和对应的结果数据存在一个对象中,调用时判断参数对应的结果是否存在,存在则直接返回结果,不存在则计算结果并保存到对象中,再返回结果。
function memoize(fn, context) {
let cache = {};
context = context || this;
return (...key) => {
if (!cache[key]) {
cache[key] = fn.apply(context, key);
}
return cache[key];
};
}
const add = (a, b) => {
console.log('add被调用了');
return a + b;
};
const calc = memoize(add);
const n1 = calc(1, 2);
const n2 = calc(1, 2);
console.log(n1, n2);
结果显示 add 函数被调用了 1 次,n2 就是缓存的结果。
应用场景:
- 执行复杂计算的函数
- 具有重复输入值的递归函数
函数式编程的优点
- 可预测性
- 纯函数总是产生相同的输出,使得代码更可预测、更容易调试。
- 可测试性
- 纯函数和无副作用使得单元测试更容易编写和维护。
- 可组合性
- 函数组合和高阶函数使得代码更模块化、更易于复用。
- 简洁性
- 函数式编程的风格通常更加简洁和清晰,使得代码更易读。