阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 函数式编程基础

函数式编程基础

作者:陈川 阅读数:19029人阅读 分类: JavaScript

函数式编程是一种编程范式,强调通过纯函数、不可变数据和函数组合来构建程序。它避免了状态变化和副作用,使得代码更易于测试和维护。在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中的mapfilterreduce都是高阶函数的例子。

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);

函数组合

函数组合是将多个函数组合成一个新函数的过程。可以通过composepipe函数实现。

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)是实现了chainflatMap方法的函子。它们用于处理副作用和异步操作。

// 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

上一篇:内存泄漏问题

下一篇:设计模式实现

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌