Skip to content

JavaScript 函数式编程实践

什么是函数式编程

函数式编程(FP)是一种编程范式,强调使用纯函数、不可变数据和函数组合来构建程序。

核心概念

1. 纯函数(Pure Functions)

纯函数是指对于相同的输入,总是返回相同的输出,且没有副作用的函数。

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

function multiply(x, y) {
  return x * y;
}

// 非纯函数(有副作用)
let counter = 0;
function increment() {
  counter++; // 修改外部状态
  return counter;
}

// 非纯函数(依赖外部状态)
function getCurrentTime() {
  return new Date(); // 每次调用返回不同值
}

TIP

纯函数的优点:

  • 易于测试
  • 易于理解
  • 易于并行执行
  • 可缓存结果

2. 不可变性(Immutability)

不可变性是指数据创建后不能被修改。

javascript
// 可变操作(不推荐)
const arr = [1, 2, 3];
arr.push(4); // 修改原数组

// 不可变操作(推荐)
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // 创建新数组

// 对象不可变操作
const obj = { name: "JavaScript", age: 25 };
const newObj = { ...obj, age: 26 }; // 创建新对象

3. 高阶函数(Higher-Order Functions)

高阶函数是接受函数作为参数或返回函数的函数。

javascript
// 接受函数作为参数
function map(array, fn) {
  const result = [];
  for (let i = 0; i < array.length; i++) {
    result.push(fn(array[i]));
  }
  return result;
}

const numbers = [1, 2, 3, 4];
const doubled = map(numbers, x => x * 2);
console.log(doubled); // [2, 4, 6, 8]

// 返回函数
function multiplyBy(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiplyBy(2);
const triple = multiplyBy(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

数组方法

map - 映射

javascript
const numbers = [1, 2, 3, 4, 5];
const squared = numbers.map(x => x * x);
console.log(squared); // [1, 4, 9, 16, 25]

const users = [
  { name: "John", age: 25 },
  { name: "Jane", age: 30 }
];
const names = users.map(user => user.name);
console.log(names); // ["John", "Jane"]

filter - 过滤

javascript
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(x => x % 2 === 0);
console.log(evens); // [2, 4, 6]

const users = [
  { name: "John", age: 25 },
  { name: "Jane", age: 30 },
  { name: "Bob", age: 17 }
];
const adults = users.filter(user => user.age >= 18);
console.log(adults); // [{ name: "John", age: 25 }, { name: "Jane", age: 30 }]

reduce - 归约

javascript
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // 15

const users = [
  { name: "John", age: 25 },
  { name: "Jane", age: 30 }
];
const totalAge = users.reduce((acc, user) => acc + user.age, 0);
console.log(totalAge); // 55

// 复杂示例:按年龄分组
const users = [
  { name: "John", age: 25 },
  { name: "Jane", age: 30 },
  { name: "Bob", age: 25 }
];
const grouped = users.reduce((acc, user) => {
  const age = user.age;
  if (!acc[age]) {
    acc[age] = [];
  }
  acc[age].push(user);
  return acc;
}, {});
console.log(grouped);
// { 25: [{ name: "John", age: 25 }, { name: "Bob", age: 25 }], 30: [{ name: "Jane", age: 30 }] }

方法链式调用

javascript
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const result = numbers
  .filter(x => x % 2 === 0)  // [2, 4, 6, 8, 10]
  .map(x => x * x)           // [4, 16, 36, 64, 100]
  .reduce((acc, curr) => acc + curr, 0); // 220

console.log(result); // 220

函数组合

基础组合

javascript
// 组合两个函数
function compose(f, g) {
  return function(x) {
    return f(g(x));
  };
}

const addOne = x => x + 1;
const multiplyByTwo = x => x * 2;

const addOneThenDouble = compose(multiplyByTwo, addOne);
console.log(addOneThenDouble(5)); // 12 (先加1,再乘2)

// 多个函数组合
function compose(...fns) {
  return function(x) {
    return fns.reduceRight((acc, fn) => fn(acc), x);
  };
}

const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

const process = pipe(
  x => x + 1,
  x => x * 2,
  x => x - 3
);
console.log(process(5)); // 9 ((5+1)*2-3)

实用组合示例

javascript
// 获取用户全名
const getFullName = user => `${user.firstName} ${user.lastName}`;

// 转换为大写
const toUpperCase = str => str.toUpperCase();

// 添加前缀
const addPrefix = prefix => str => `${prefix}${str}`;

// 组合函数
const formatUserName = pipe(
  getFullName,
  toUpperCase,
  addPrefix("USER: ")
);

const user = { firstName: "John", lastName: "Doe" };
console.log(formatUserName(user)); // "USER: JOHN DOE"

柯里化(Currying)

柯里化是将多参数函数转换为单参数函数序列的技术。

javascript
// 普通函数
function add(a, b, c) {
  return a + b + c;
}

// 柯里化函数
function curryAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

console.log(curryAdd(1)(2)(3)); // 6

// 通用柯里化函数
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

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

偏函数(Partial Application)

偏函数是固定函数的一些参数,创建一个新函数。

javascript
function partial(fn, ...fixedArgs) {
  return function(...remainingArgs) {
    return fn.apply(this, fixedArgs.concat(remainingArgs));
  };
}

function multiply(a, b, c) {
  return a * b * c;
}

const multiplyBy2And3 = partial(multiply, 2, 3);
console.log(multiplyBy2And3(4)); // 24 (2 * 3 * 4)

函子(Functors)

函子是实现了 map 方法的对象。

javascript
// 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 maybe = Maybe.of(5)
  .map(x => x * 2)
  .map(x => x + 1)
  .getOrElse(0);
console.log(maybe); // 11

const maybeNull = Maybe.of(null)
  .map(x => x * 2)
  .getOrElse(0);
console.log(maybeNull); // 0

实际应用

数据处理管道

javascript
const data = [
  { name: "John", age: 25, score: 85 },
  { name: "Jane", age: 30, score: 92 },
  { name: "Bob", age: 20, score: 78 },
  { name: "Alice", age: 28, score: 95 }
];

// 函数式处理
const getHighScorers = pipe(
  users => users.filter(user => user.score >= 90),
  users => users.map(user => ({ ...user, grade: "A" })),
  users => users.sort((a, b) => b.score - a.score)
);

const result = getHighScorers(data);
console.log(result);

事件处理

javascript
// 函数式事件处理
const handleClick = pipe(
  e => e.target.value,
  value => value.trim(),
  value => value.toUpperCase(),
  value => console.log("处理后的值:", value)
);

button.addEventListener("click", handleClick);

总结

函数式编程核心要点:

  • 纯函数:无副作用,相同输入相同输出
  • 不可变性:不修改原数据,创建新数据
  • 高阶函数:函数作为参数或返回值
  • 函数组合:将小函数组合成复杂功能
  • 柯里化:将多参数函数转换为单参数序列
  • 数组方法:map、filter、reduce 等