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 等