您现在的位置是:网站首页 > 函数式编程基础文章详情

函数式编程基础

函数式编程是一种编程范式,强调通过纯函数、不可变数据和函数组合来构建程序。它避免了状态变化和副作用,使得代码更易于测试和维护。在JavaScript中,函数式编程的特性如高阶函数、闭包和柯里化等,为开发者提供了强大的工具。

纯函数与副作用

纯函数是指对于相同的输入,总是返回相同的输出,并且不产生任何副作用的函数。副作用包括修改外部变量、打印日志、发起网络请求等。纯函数的好处是可预测性和可测试性。

// 纯函数示例
function add(a, b) {
  return a + b;
}

// 非纯函数示例(依赖外部变量)
let counter = 0;
function increment() {
  counter++;
  return counter;
}

不可变数据

不可变数据是指一旦创建就不能被修改的数据。在JavaScript中,可以通过Object.freeze或第三方库(如Immutable.js)来实现不可变性。不可变数据有助于避免意外的状态变化。

// 使用Object.freeze实现浅不可变性
const person = Object.freeze({
  name: 'Alice',
  age: 30
});

// 尝试修改会静默失败(严格模式下会报错)
person.age = 31; // 无效

高阶函数

高阶函数是指接受一个或多个函数作为参数,或者返回一个函数的函数。JavaScript中的mapfilterreduce都是高阶函数的典型例子。

// 高阶函数示例:map
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2); // [2, 4, 6]

// 自定义高阶函数
function repeat(fn, times) {
  return function(...args) {
    for (let i = 0; i < times; i++) {
      fn(...args);
    }
  };
}

const sayHelloThreeTimes = repeat(console.log, 3);
sayHelloThreeTimes('Hello'); // 打印三次"Hello"

函数组合

函数组合是将多个简单函数组合成一个更复杂函数的过程。可以通过composepipe函数来实现。组合后的函数从右向左(compose)或从左向右(pipe)依次执行。

// 简单的compose实现
function compose(...fns) {
  return function(x) {
    return fns.reduceRight((acc, fn) => fn(acc), x);
  };
}

const add1 = x => x + 1;
const multiply2 = x => x * 2;
const addThenMultiply = compose(multiply2, add1);

console.log(addThenMultiply(3)); // (3 + 1) * 2 = 8

柯里化与部分应用

柯里化是将一个多参数函数转换为一系列单参数函数的过程。部分应用是固定函数的部分参数,生成一个接受剩余参数的新函数。

// 柯里化示例
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6

// 部分应用示例
const addFive = curriedSum(2)(3);
console.log(addFive(5)); // 10

递归与尾调用优化

函数式编程中常用递归代替循环。ES6引入了尾调用优化(TCO),使得某些递归调用不会增加调用栈。

// 递归实现阶乘
function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1); // 不是尾调用
}

// 尾递归优化版本
function factorialTail(n, acc = 1) {
  if (n <= 1) return acc;
  return factorialTail(n - 1, n * acc); // 尾调用
}

函子与单子

函子(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));
  }
  
  getOrElse(defaultValue) {
    return this.value == null ? defaultValue : this.value;
  }
}

// 使用示例
const safeDivide = (a, b) => b === 0 ? Maybe.of(null) : Maybe.of(a / b);
const result = safeDivide(10, 2)
  .map(x => x * 3)
  .getOrElse('Error: Division by zero');
console.log(result); // 15

惰性求值与无限序列

惰性求值延迟表达式的计算直到真正需要结果时。可以用于创建无限序列或优化性能。

// 简单的惰性求值示例
function lazy(fn) {
  let evaluated = false;
  let result;
  return function() {
    if (!evaluated) {
      result = fn();
      evaluated = true;
    }
    return result;
  };
}

const expensiveComputation = lazy(() => {
  console.log('Computing...');
  return 42;
});

console.log(expensiveComputation()); // 打印"Computing..."然后返回42
console.log(expensiveComputation()); // 直接返回42

模式匹配与代数数据类型

虽然JavaScript没有内置的模式匹配,但可以通过库或特定编码模式模拟代数数据类型(ADT)。

// 模拟代数数据类型
const Shape = {
  Circle(radius) {
    return { type: 'Circle', radius };
  },
  Rectangle(width, height) {
    return { type: 'Rectangle', width, height };
  }
};

function area(shape) {
  switch (shape.type) {
    case 'Circle':
      return Math.PI * shape.radius ** 2;
    case 'Rectangle':
      return shape.width * shape.height;
    default:
      throw new Error('Unknown shape');
  }
}

const circle = Shape.Circle(5);
console.log(area(circle)); // ~78.54

函数式编程在实际项目中的应用

在实际项目中,函数式编程可以用于状态管理、数据处理和UI构建等方面。Redux就是一个典型的函数式状态管理库。

// Redux风格的reducer
function counterReducer(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

// 使用
let state = 0;
state = counterReducer(state, { type: 'INCREMENT' });
console.log(state); // 1

性能考量与权衡

虽然函数式编程有很多优点,但也需要注意性能问题。例如,频繁创建新对象可能导致内存压力,深度递归可能导致栈溢出。

// 性能对比:命令式 vs 函数式
const largeArray = Array(1000000).fill().map((_, i) => i);

// 命令式(更快)
let sum = 0;
for (let i = 0; i < largeArray.length; i++) {
  sum += largeArray[i];
}

// 函数式(更慢但更声明式)
const functionalSum = largeArray.reduce((acc, x) => acc + x, 0);

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步