函数式编程基础
函数式编程是一种编程范式,强调通过纯函数、不可变数据和函数组合来构建程序。它避免了状态变化和副作用,使得代码更易于测试和维护。在JavaScript中,函数式编程的特性如高阶函数、闭包和柯里化被广泛使用。
纯函数与副作用
纯函数是指相同的输入始终返回相同的输出,并且不产生副作用的函数。副作用包括修改外部变量、发起网络请求或操作DOM等。纯函数更容易推理和测试。
// 纯函数
function add(a, b) {
return a + b;
}
// 非纯函数(有副作用)
let counter = 0;
function increment() {
counter++;
}
不可变数据
不可变数据是指创建后不能被修改的数据。在JavaScript中,原始类型(如字符串、数字)是不可变的,但对象和数组是可变的。函数式编程鼓励使用不可变数据来避免意外的修改。
// 可变操作
const arr = [1, 2, 3];
arr.push(4); // 修改原数组
// 不可变操作
const newArr = [...arr, 4]; // 创建新数组
高阶函数
高阶函数是指接受函数作为参数或返回函数的函数。JavaScript中的map
、filter
和reduce
都是高阶函数的例子。
const numbers = [1, 2, 3, 4];
// map
const doubled = numbers.map(n => n * 2);
// filter
const evens = numbers.filter(n => n % 2 === 0);
// reduce
const sum = numbers.reduce((acc, n) => acc + n, 0);
函数组合
函数组合是将多个函数组合成一个新函数的过程。可以通过compose
或pipe
函数实现。
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const addThenMultiply = pipe(add1, multiply2);
console.log(addThenMultiply(3)); // 8
柯里化
柯里化是将多参数函数转换为一系列单参数函数的技术。柯里化后的函数可以更容易地复用和组合。
const curry = fn => {
const arity = fn.length;
return function curried(...args) {
if (args.length >= arity) return fn(...args);
return (...moreArgs) => curried(...args, ...moreArgs);
};
};
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
递归
递归是函数调用自身的技术。在函数式编程中,递归常用来替代循环。
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
惰性求值
惰性求值是指延迟表达式的计算直到真正需要它的值。可以通过生成器函数实现惰性序列。
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
函子与单子
函子(Functor)是实现了map
方法的类型,单子(Monad)是实现了chain
或flatMap
方法的函子。它们用于处理副作用和异步操作。
// Maybe函子
class Maybe {
constructor(value) {
this.value = value;
}
static of(value) {
return new Maybe(value);
}
map(fn) {
return this.value == null ? Maybe.of(null) : Maybe.of(fn(this.value));
}
}
const maybeNum = Maybe.of(5).map(x => x * 2);
console.log(maybeNum.value); // 10
模式匹配
模式匹配是根据数据的结构选择不同处理逻辑的技术。JavaScript中没有原生支持,但可以通过库或函数模拟。
const match = (value, patterns) => {
for (const [predicate, handler] of patterns) {
if (predicate(value)) return handler(value);
}
throw new Error('No pattern matched');
};
const result = match(3, [
[x => x === 0, () => 'zero'],
[x => x > 0, x => `positive ${x}`],
[x => x < 0, x => `negative ${x}`]
]);
console.log(result); // "positive 3"
持久化数据结构
持久化数据结构在修改时保留之前的版本,实现结构共享。这可以通过库如Immutable.js实现。
import { List } from 'immutable';
const list1 = List([1, 2, 3]);
const list2 = list1.push(4);
console.log(list1.toJS()); // [1, 2, 3]
console.log(list2.toJS()); // [1, 2, 3, 4]
类型系统与函数式编程
TypeScript等类型系统可以帮助捕获函数式编程中的错误,如不正确的函数组合或类型不匹配。
type AddFn = (a: number, b: number) => number;
const add: AddFn = (a, b) => a + b;
type ComposeFn = <A, B, C>(f: (b: B) => C, g: (a: A) => B) => (a: A) => C;
const compose: ComposeFn = (f, g) => x => f(g(x));
性能考虑
函数式编程可能带来性能开销,如创建新对象和数组。在性能敏感的场景需要权衡。
// 性能较差的不可变操作
const largeArray = Array(1000000).fill(0);
const newArray = largeArray.map(x => x + 1); // 创建新数组
// 性能更好的可变操作(必要时)
for (let i = 0; i < largeArray.length; i++) {
largeArray[i]++;
}
函数式编程库
JavaScript生态系统中有许多函数式编程库,如Ramda、Lodash/fp和Folktale。
import R from 'ramda';
const addThenMultiply = R.pipe(
R.add(1),
R.multiply(2)
);
console.log(addThenMultiply(3)); // 8
异步编程与函数式
Promise和async/await可以与函数式编程结合,处理异步操作。
const fetchData = url => fetch(url).then(res => res.json());
const processData = R.pipe(
fetchData,
R.then(R.map(R.prop('name'))),
R.then(R.filter(R.startsWith('A')))
);
processData('https://api.example.com/users')
.then(console.log);
测试与调试
纯函数和不可变数据使测试和调试更容易,因为代码的行为更可预测。
// 纯函数易于测试
function testAdd() {
console.assert(add(1, 2) === 3, 'add should work');
}
// 非纯函数难以测试
function testIncrement() {
counter = 0;
increment();
console.assert(counter === 1, 'increment should work');
}
函数式反应式编程
函数式反应式编程(FRP)将函数式编程与反应式编程结合,用于处理事件流。
import { fromEvent } from 'rxjs';
import { map, filter } from 'rxjs/operators';
const clicks = fromEvent(document, 'click');
const positions = clicks.pipe(
filter(evt => evt.clientX > 100),
map(evt => ({ x: evt.clientX, y: evt.clientY }))
);
positions.subscribe(pos => console.log(pos));
领域建模
函数式编程可以用于领域建模,通过代数数据类型和模式匹配表达业务逻辑。
class Result {
static ok(value) {
return new Ok(value);
}
static error(message) {
return new Error(message);
}
}
class Ok extends Result {
constructor(value) {
super();
this.value = value;
}
map(fn) {
return Result.ok(fn(this.value));
}
}
class Error extends Result {
constructor(message) {
super();
this.message = message;
}
map() {
return this;
}
}
const divide = (a, b) =>
b === 0 ? Result.error('Division by zero') : Result.ok(a / b);
divide(10, 2).map(x => x * 3); // Ok(15)
divide(10, 0).map(x => x * 3); // Error("Division by zero")
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn