JavaScript 函数式编程完全指南
📚 目录
1. 什么是函数式编程
函数式编程(Functional Programming,FP)是一种编程范式,它将计算视为数学函数的求值,强调:
- 纯函数:相同输入总是产生相同输出
- 不可变性:数据一旦创建就不能被修改
- 函数组合:通过组合简单函数构建复杂功能
- 避免副作用:函数不应该改变外部状态
// ❌ 命令式编程(告诉计算机"怎么做")
const numbers = [1, 2, 3, 4, 5];
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
console.log(doubled); // [2, 4, 6, 8, 10]
// ✅ 函数式编程(告诉计算机"做什么")
const numbersFP = [1, 2, 3, 4, 5];
const doubledFP = numbersFP.map(n => n * 2);
console.log(doubledFP); // [2, 4, 6, 8, 10]
2. 纯函数
2.1 纯函数的定义
纯函数满足两个条件:
- 确定性:相同的输入永远返回相同的输出
- 无副作用:不修改外部状态,不依赖外部可变状态
// ✅ 纯函数示例
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
function greet(name) {
return `Hello, ${name}!`;
}
// 多次调用,结果永远相同
console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(greet('Alice')); // "Hello, Alice!"
2.2 非纯函数示例
// ❌ 非纯函数:依赖外部变量
let taxRate = 0.1;
function calculateTax(amount) {
return amount * taxRate; // 依赖外部变量 taxRate
}
console.log(calculateTax(100)); // 10
taxRate = 0.2;
console.log(calculateTax(100)); // 20 - 相同输入,不同输出!
// ❌ 非纯函数:修改外部状态
let total = 0;
function addToTotal(value) {
total += value; // 副作用:修改了外部变量
return total;
}
console.log(addToTotal(5)); // 5
console.log(addToTotal(5)); // 10 - 相同输入,不同输出!
// ❌ 非纯函数:修改输入参数
function addItem(cart, item) {
cart.push(item); // 副作用:修改了传入的数组
return cart;
}
const myCart = ['apple'];
addItem(myCart, 'banana');
console.log(myCart); // ['apple', 'banana'] - 原数组被修改了!
2.3 将非纯函数转换为纯函数
// ✅ 纯函数版本:将依赖作为参数传入
function calculateTaxPure(amount, taxRate) {
return amount * taxRate;
}
console.log(calculateTaxPure(100, 0.1)); // 10
console.log(calculateTaxPure(100, 0.2)); // 20
// ✅ 纯函数版本:返回新值而不是修改外部状态
function addToTotalPure(currentTotal, value) {
return currentTotal + value;
}
let totalPure = 0;
totalPure = addToTotalPure(totalPure, 5);
console.log(totalPure); // 5
// ✅ 纯函数版本:返回新数组而不是修改原数组
function addItemPure(cart, item) {
return [...cart, item]; // 返回新数组
}
const myCartPure = ['apple'];
const newCart = addItemPure(myCartPure, 'banana');
console.log(myCartPure); // ['apple'] - 原数组未被修改
console.log(newCart); // ['apple', 'banana']
2.4 纯函数的好处
// 1. 可测试性 - 纯函数非常容易测试
function calculateDiscount(price, discountPercent) {
return price * (1 - discountPercent / 100);
}
// 测试用例
console.log(calculateDiscount(100, 20) === 80); // true
console.log(calculateDiscount(50, 10) === 45); // true
console.log(calculateDiscount(200, 50) === 100); // true
// 2. 可缓存性 - 相同输入总是相同输出,可以缓存结果
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('从缓存获取');
return cache.get(key);
}
console.log('计算中...');
const result = fn(...args);
cache.set(key, result);
return result;
};
}
function expensiveCalculation(n) {
// 模拟耗时计算
let result = 0;
for (let i = 0; i < n; i++) {
result += i;
}
return result;
}
const memoizedCalc = memoize(expensiveCalculation);
console.log(memoizedCalc(10000)); // 计算中... 49995000
console.log(memoizedCalc(10000)); // 从缓存获取 49995000
console.log(memoizedCalc(5000)); // 计算中... 12497500
3. 不可变性
3.1 什么是不可变性
不可变性意味着数据一旦创建,就不能被修改。任何"修改"操作都会返回新的数据。
// ❌ 可变操作
const person = { name: 'Alice', age: 25 };
person.age = 26; // 直接修改原对象
console.log(person); // { name: 'Alice', age: 26 }
// ✅ 不可变操作
const personImmutable = { name: 'Alice', age: 25 };
const updatedPerson = { ...personImmutable, age: 26 }; // 创建新对象
console.log(personImmutable); // { name: 'Alice', age: 25 } - 原对象不变
console.log(updatedPerson); // { name: 'Alice', age: 26 }
3.2 数组的不可变操作
const fruits = ['apple', 'banana', 'orange'];
// ❌ 可变方法(会修改原数组)
// push, pop, shift, unshift, splice, sort, reverse
// ✅ 不可变方法(返回新数组)
// map, filter, reduce, concat, slice, spread operator
// 添加元素
const withGrape = [...fruits, 'grape'];
console.log(fruits); // ['apple', 'banana', 'orange']
console.log(withGrape); // ['apple', 'banana', 'orange', 'grape']
// 在开头添加
const withMango = ['mango', ...fruits];
console.log(withMango); // ['mango', 'apple', 'banana', 'orange']
// 删除元素(通过 filter)
const withoutBanana = fruits.filter(f => f !== 'banana');
console.log(withoutBanana); // ['apple', 'orange']
// 修改元素(通过 map)
const upperFruits = fruits.map(f => f.toUpperCase());
console.log(upperFruits); // ['APPLE', 'BANANA', 'ORANGE']
// 在指定位置插入
const insertAt = (arr, index, item) => [
...arr.slice(0, index),
item,
...arr.slice(index)
];
console.log(insertAt(fruits, 1, 'kiwi')); // ['apple', 'kiwi', 'banana', 'orange']
// 删除指定位置的元素
const removeAt = (arr, index) => [
...arr.slice(0, index),
...arr.slice(index + 1)
];
console.log(removeAt(fruits, 1)); // ['apple', 'orange']
// 更新指定位置的元素
const updateAt = (arr, index, newValue) =>
arr.map((item, i) => i === index ? newValue : item);
console.log(updateAt(fruits, 1, 'blueberry')); // ['apple', 'blueberry', 'orange']
3.3 对象的不可变操作
const user = {
name: 'Alice',
age: 25,
address: {
city: 'Beijing',
country: 'China'
},
hobbies: ['reading', 'coding']
};
// 更新顶层属性
const userWithNewAge = { ...user, age: 26 };
console.log(user.age); // 25
console.log(userWithNewAge.age); // 26
// 添加新属性
const userWithEmail = { ...user, email: 'alice@example.com' };
console.log(userWithEmail);
// 删除属性
const { age, ...userWithoutAge } = user;
console.log(userWithoutAge); // { name: 'Alice', address: {...}, hobbies: [...] }
// 更新嵌套属性(需要深层展开)
const userWithNewCity = {
...user,
address: {
...user.address,
city: 'Shanghai'
}
};
console.log(user.address.city); // 'Beijing'
console.log(userWithNewCity.address.city); // 'Shanghai'
// 更新数组属性
const userWithNewHobby = {
...user,
hobbies: [...user.hobbies, 'gaming']
};
console.log(user.hobbies); // ['reading', 'coding']
console.log(userWithNewHobby.hobbies); // ['reading', 'coding', 'gaming']
3.4 深度不可变更新工具函数
// 通用的深度更新函数
function updatePath(obj, path, value) {
const keys = path.split('.');
if (keys.length === 1) {
return { ...obj, [keys[0]]: value };
}
const [first, ...rest] = keys;
return {
...obj,
[first]: updatePath(obj[first], rest.join('.'), value)
};
}
const state = {
user: {
profile: {
name: 'Alice',
settings: {
theme: 'dark',
language: 'en'
}
}
}
};
const newState = updatePath(state, 'user.profile.settings.theme', 'light');
console.log(state.user.profile.settings.theme); // 'dark'
console.log(newState.user.profile.settings.theme); // 'light'
// 使用 Object.freeze 强制不可变(浅层)
const frozenObj = Object.freeze({ a: 1, b: 2 });
// frozenObj.a = 100; // 严格模式下会报错,非严格模式静默失败
console.log(frozenObj.a); // 1
// 深度冻结
function deepFreeze(obj) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
deepFreeze(obj[key]);
}
});
return Object.freeze(obj);
}
const deepFrozenObj = deepFreeze({
a: 1,
b: { c: 2, d: { e: 3 } }
});
4. 高阶函数
4.1 什么是高阶函数
高阶函数是至少满足以下条件之一的函数:
- 接受函数作为参数
- 返回一个函数
// 接受函数作为参数
function executeOperation(a, b, operation) {
return operation(a, b);
}
const add = (x, y) => x + y;
const multiply = (x, y) => x * y;
const subtract = (x, y) => x - y;
console.log(executeOperation(5, 3, add)); // 8
console.log(executeOperation(5, 3, multiply)); // 15
console.log(executeOperation(5, 3, subtract)); // 2
// 返回一个函数
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
const tenTimes = createMultiplier(10);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(tenTimes(5)); // 50
4.2 常用高阶函数模式
// 1. 函数包装器 - 添加额外功能
function withLogging(fn) {
return function(...args) {
console.log(`调用函数,参数: ${JSON.stringify(args)}`);
const result = fn(...args);
console.log(`返回结果: ${result}`);
return result;
};
}
function addNumbers(a, b) {
return a + b;
}
const addWithLogging = withLogging(addNumbers);
addWithLogging(3, 4);
// 输出:
// 调用函数,参数: [3,4]
// 返回结果: 7
// 2. 函数计时器
function withTiming(fn) {
return function(...args) {
const start = performance.now();
const result = fn(...args);
const end = performance.now();
console.log(`执行时间: ${(end - start).toFixed(2)}ms`);
return result;
};
}
function slowFunction() {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
}
const timedSlowFunction = withTiming(slowFunction);
timedSlowFunction(); // 执行时间: x.xxms
// 3. 只执行一次的函数
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn(...args);
}
return result;
};
}
const initialize = once(() => {
console.log('初始化中...');
return { initialized: true };
});
console.log(initialize()); // 初始化中... { initialized: true }
console.log(initialize()); // { initialized: true } - 不再打印"初始化中..."
console.log(initialize()); // { initialized: true }
// 4. 防抖函数
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const debouncedSearch = debounce((query) => {
console.log(`搜索: ${query}`);
}, 300);
// 快速连续调用,只有最后一次会执行
debouncedSearch('a');
debouncedSearch('ab');
debouncedSearch('abc');
// 300ms 后只输出: 搜索: abc
// 5. 节流函数
function throttle(fn, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
const throttledScroll = throttle(() => {
console.log('处理滚动事件');
}, 100);
4.3 创建专用函数
// 使用高阶函数创建专用函数
function createValidator(validationFn, errorMessage) {
return function(value) {
if (validationFn(value)) {
return { valid: true, value };
}
return { valid: false, error: errorMessage };
};
}
const isNotEmpty = createValidator(
value => value && value.trim().length > 0,
'值不能为空'
);
const isEmail = createValidator(
value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
'请输入有效的邮箱地址'
);
const isMinLength = (min) => createValidator(
value => value && value.length >= min,
`长度至少为 ${min} 个字符`
);
console.log(isNotEmpty('hello')); // { valid: true, value: 'hello' }
console.log(isNotEmpty('')); // { valid: false, error: '值不能为空' }
console.log(isEmail('test@mail.com')); // { valid: true, value: 'test@mail.com' }
console.log(isEmail('invalid')); // { valid: false, error: '请输入有效的邮箱地址' }
const isMinLength5 = isMinLength(5);
console.log(isMinLength5('hello')); // { valid: true, value: 'hello' }
console.log(isMinLength5('hi')); // { valid: false, error: '长度至少为 5 个字符' }
5. 核心数组方法
5.1 map - 转换每个元素
// map 的基本用法
const numbers = [1, 2, 3, 4, 5];
// 简单转换
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
const squared = numbers.map(n => n ** 2);
console.log(squared); // [1, 4, 9, 16, 25]
// 转换对象数组
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 }
];
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob', 'Charlie']
const userCards = users.map(user => ({
...user,
displayName: `${user.name} (${user.age}岁)`
}));
console.log(userCards);
// [
// { name: 'Alice', age: 25, displayName: 'Alice (25岁)' },
// { name: 'Bob', age: 30, displayName: 'Bob (30岁)' },
// { name: 'Charlie', age: 35, displayName: 'Charlie (35岁)' }
// ]
// 使用索引参数
const indexed = numbers.map((n, index) => `${index}: ${n}`);
console.log(indexed); // ['0: 1', '1: 2', '2: 3', '3: 4', '4: 5']
// 自己实现 map
function myMap(arr, fn) {
const result = [];
for (let i = 0; i < arr.length; i++) {
result.push(fn(arr[i], i, arr));
}
return result;
}
console.log(myMap([1, 2, 3], x => x * 10)); // [10, 20, 30]
5.2 filter - 过滤元素
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 基本过滤
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
const odds = numbers.filter(n => n % 2 !== 0);
console.log(odds); // [1, 3, 5, 7, 9]
const greaterThan5 = numbers.filter(n => n > 5);
console.log(greaterThan5); // [6, 7, 8, 9, 10]
// 过滤对象数组
const products = [
{ name: 'iPhone', price: 999, inStock: true },
{ name: 'iPad', price: 799, inStock: false },
{ name: 'MacBook', price: 1299, inStock: true },
{ name: 'AirPods', price: 199, inStock: true }
];
const inStockProducts = products.filter(p => p.inStock);
console.log(inStockProducts);
// [
// { name: 'iPhone', price: 999, inStock: true },
// { name: 'MacBook', price: 1299, inStock: true },
// { name: 'AirPods', price: 199, inStock: true }
// ]
const affordableProducts = products.filter(p => p.price < 1000);
console.log(affordableProducts);
// [
// { name: 'iPhone', price: 999, inStock: true },
// { name: 'iPad', price: 799, inStock: false },
// { name: 'AirPods', price: 199, inStock: true }
// ]
// 组合条件
const affordableInStock = products.filter(p => p.price < 1000 && p.inStock);
console.log(affordableInStock);
// [
// { name: 'iPhone', price: 999, inStock: true },
// { name: 'AirPods', price: 199, inStock: true }
// ]
// 去除假值
const mixedArray = [0, 1, '', 'hello', null, undefined, false, true, NaN];
const truthyValues = mixedArray.filter(Boolean);
console.log(truthyValues); // [1, 'hello', true]
// 去重
const duplicates = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];
const unique = duplicates.filter((item, index, arr) => arr.indexOf(item) === index);
console.log(unique); // [1, 2, 3, 4]
// 自己实现 filter
function myFilter(arr, predicate) {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (predicate(arr[i], i, arr)) {
result.push(arr[i]);
}
}
return result;
}
console.log(myFilter([1, 2, 3, 4, 5], x => x > 2)); // [3, 4, 5]
5.3 reduce - 归约为单一值
// 基本用法:求和
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, current) => {
console.log(`accumulator: ${accumulator}, current: ${current}`);
return accumulator + current;
}, 0);
// accumulator: 0, current: 1
// accumulator: 1, current: 2
// accumulator: 3, current: 3
// accumulator: 6, current: 4
// accumulator: 10, current: 5
console.log(sum); // 15
// 求乘积
const product = numbers.reduce((acc, cur) => acc * cur, 1);
console.log(product); // 120
// 找最大值
const max = numbers.reduce((acc, cur) => cur > acc ? cur : acc, -Infinity);
console.log(max); // 5
// 找最小值
const min = numbers.reduce((acc, cur) => cur < acc ? cur : acc, Infinity);
console.log(min); // 1
// 数组转对象
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
const userMap = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
console.log(userMap);
// {
// 1: { id: 1, name: 'Alice' },
// 2: { id: 2, name: 'Bob' },
// 3: { id: 3, name: 'Charlie' }
// }
// 分组
const people = [
{ name: 'Alice', age: 25, city: 'Beijing' },
{ name: 'Bob', age: 30, city: 'Shanghai' },
{ name: 'Charlie', age: 25, city: 'Beijing' },
{ name: 'David', age: 30, city: 'Beijing' }
];
const groupByAge = people.reduce((acc, person) => {
const key = person.age;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(person);
return acc;
}, {});
console.log(groupByAge);
// {
// 25: [{ name: 'Alice', ... }, { name: 'Charlie', ... }],
// 30: [{ name: 'Bob', ... }, { name: 'David', ... }]
// }
// 通用分组函数
function groupBy(arr, keyFn) {
return arr.reduce((acc, item) => {
const key = keyFn(item);
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(item);
return acc;
}, {});
}
console.log(groupBy(people, p => p.city));
// {
// Beijing: [Alice, Charlie, David],
// Shanghai: [Bob]
// }
// 统计出现次数
const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const wordCount = words.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1;
return acc;
}, {});
console.log(wordCount); // { apple: 3, banana: 2, orange: 1 }
// 扁平化数组
const nested = [[1, 2], [3, 4], [5, 6]];
const flattened = nested.reduce((acc, arr) => [...acc, ...arr], []);
console.log(flattened); // [1, 2, 3, 4, 5, 6]
// 深度扁平化
const deepNested = [[1, [2, 3]], [4, [5, [6, 7]]]];
function flatten(arr) {
return arr.reduce((acc, item) => {
if (Array.isArray(item)) {
return [...acc, ...flatten(item)];
}
return [...acc, item];
}, []);
}
console.log(flatten(deepNested)); // [1, 2, 3, 4, 5, 6, 7]
// 使用 reduce 实现 map
function mapWithReduce(arr, fn) {
return arr.reduce((acc, item, index) => {
acc.push(fn(item, index, arr));
return acc;
}, []);
}
console.log(mapWithReduce([1, 2, 3], x => x * 2)); // [2, 4, 6]
// 使用 reduce 实现 filter
function filterWithReduce(arr, predicate) {
return arr.reduce((acc, item, index) => {
if (predicate(item, index, arr)) {
acc.push(item);
}
return acc;
}, []);
}
console.log(filterWithReduce([1, 2, 3, 4, 5], x => x > 2)); // [3, 4, 5]
// 自己实现 reduce
function myReduce(arr, reducer, initialValue) {
let accumulator = initialValue;
let startIndex = 0;
if (arguments.length < 3) {
accumulator = arr[0];
startIndex = 1;
}
for (let i = startIndex; i < arr.length; i++) {
accumulator = reducer(accumulator, arr[i], i, arr);
}
return accumulator;
}
console.log(myReduce([1, 2, 3, 4, 5], (a, b) => a + b, 0)); // 15
5.4 其他有用的数组方法
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// find - 找到第一个满足条件的元素
const firstEven = numbers.find(n => n % 2 === 0);
console.log(firstEven); // 2
const firstGreaterThan5 = numbers.find(n => n > 5);
console.log(firstGreaterThan5); // 6
// findIndex - 找到第一个满足条件的元素的索引
const firstEvenIndex = numbers.findIndex(n => n % 2 === 0);
console.log(firstEvenIndex); // 1
// some - 检查是否至少有一个元素满足条件
const hasEven = numbers.some(n => n % 2 === 0);
console.log(hasEven); // true
const hasNegative = numbers.some(n => n < 0);
console.log(hasNegative); // false
// every - 检查是否所有元素都满足条件
const allPositive = numbers.every(n => n > 0);
console.log(allPositive); // true
const allEven = numbers.every(n => n % 2 === 0);
console.log(allEven); // false
// includes - 检查数组是否包含某个值
console.log(numbers.includes(5)); // true
console.log(numbers.includes(11)); // false
// flat - 扁平化数组
const nestedArray = [1, [2, 3], [4, [5, 6]]];
console.log(nestedArray.flat()); // [1, 2, 3, 4, [5, 6]]
console.log(nestedArray.flat(2)); // [1, 2, 3, 4, 5, 6]
console.log(nestedArray.flat(Infinity)); // [1, 2, 3, 4, 5, 6]
// flatMap - map + flat(1)
const sentences = ['Hello World', 'Goodbye World'];
const words = sentences.flatMap(s => s.split(' '));
console.log(words); // ['Hello', 'World', 'Goodbye', 'World']
// 实用示例:处理可能返回数组的映射
const data = [1, 2, 3];
const duplicated = data.flatMap(n => [n, n]);
console.log(duplicated); // [1, 1, 2, 2, 3, 3]
5.5 方法链式调用
const orders = [
{ id: 1, customer: 'Alice', items: ['apple', 'banana'], total: 25, status: 'completed' },
{ id: 2, customer: 'Bob', items: ['orange'], total: 15, status: 'pending' },
{ id: 3, customer: 'Charlie', items: ['apple', 'grape', 'melon'], total: 45, status: 'completed' },
{ id: 4, customer: 'David', items: ['banana'], total: 10, status: 'cancelled' },
{ id: 5, customer: 'Eve', items: ['apple', 'orange'], total: 30, status: 'completed' }
];
// 链式调用:筛选已完成订单,计算平均订单金额
const averageCompletedOrderValue = orders
.filter(order => order.status === 'completed')
.map(order => order.total)
.reduce((sum, total, _, arr) => sum + total / arr.length, 0);
console.log(averageCompletedOrderValue); // 33.33...
// 获取所有完成订单的商品列表(去重)
const completedOrderItems = orders
.filter(order => order.status === 'completed')
.flatMap(order => order.items)
.filter((item, index, arr) => arr.indexOf(item) === index);
console.log(completedOrderItems); // ['apple', 'banana', 'grape', 'melon', 'orange']
// 创建订单摘要
const orderSummary = orders
.filter(order => order.status !== 'cancelled')
.map(order => ({
orderId: order.id,
customer: order.customer,
itemCount: order.items.length,
total: order.total
}))
.sort((a, b) => b.total - a.total);
console.log(orderSummary);
// [
// { orderId: 3, customer: 'Charlie', itemCount: 3, total: 45 },
// { orderId: 5, customer: 'Eve', itemCount: 2, total: 30 },
// { orderId: 1, customer: 'Alice', itemCount: 2, total: 25 },
// { orderId: 2, customer: 'Bob', itemCount: 1, total: 15 }
// ]
// 按状态分组统计
const statusStats = orders.reduce((acc, order) => {
if (!acc[order.status]) {
acc[order.status] = { count: 0, totalValue: 0 };
}
acc[order.status].count++;
acc[order.status].totalValue += order.total;
return acc;
}, {});
console.log(statusStats);
// {
// completed: { count: 3, totalValue: 100 },
// pending: { count: 1, totalValue: 15 },
// cancelled: { count: 1, totalValue: 10 }
// }
6. 函数组合与管道
6.1 函数组合 (Compose)
函数组合是将多个函数合并成一个函数,从右到左执行。
// 基本概念
// compose(f, g, h)(x) 等价于 f(g(h(x)))
// 简单的两个函数组合
const compose2 = (f, g) => x => f(g(x));
const addOne = x => x + 1;
const double = x => x * 2;
const addOneThenDouble = compose2(double, addOne); // 先加1,再乘2
console.log(addOneThenDouble(5)); // (5 + 1) * 2 = 12
const doubleThenAddOne = compose2(addOne, double); // 先乘2,再加1
console.log(doubleThenAddOne(5)); // (5 * 2) + 1 = 11
// 通用的 compose 函数(支持多个函数)
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const square = x => x ** 2;
const negate = x => -x;
const composed = compose(negate, square, addOne, double);
// 执行顺序: double -> addOne -> square -> negate
// 5 -> 10 -> 11 -> 121 -> -121
console.log(composed(5)); // -121
// 更复杂的例子:处理字符串
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const split = delimiter => str => str.split(delimiter);
const join = delimiter => arr => arr.join(delimiter);
const map = fn => arr => arr.map(fn);
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
const slugify = compose(
join('-'),
map(toLowerCase),
split(' '),
trim
);
console.log(slugify(' Hello World ')); // 'hello-world'
console.log(slugify('JavaScript Is Awesome')); // 'javascript-is-awesome'
6.2 管道 (Pipe)
管道与组合类似,但从左到右执行,更符合阅读习惯。
// pipe(f, g, h)(x) 等价于 h(g(f(x)))
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x ** 2;
const piped = pipe(double, addOne, square);
// 执行顺序: double -> addOne -> square
// 5 -> 10 -> 11 -> 121
console.log(piped(5)); // 121
// 实际应用示例:数据处理管道
const users = [
{ name: 'alice', age: 25, role: 'admin' },
{ name: 'bob', age: 30, role: 'user' },
{ name: 'charlie', age: 35, role: 'admin' },
{ name: 'david', age: 28, role: 'user' }
];
// 辅助函数
const filter = predicate => arr => arr.filter(predicate);
const map = fn => arr => arr.map(fn);
const sortBy = key => arr => [...arr].sort((a, b) => a[key] - b[key]);
const take = n => arr => arr.slice(0, n);
// 构建处理管道
const getTopAdminNames = pipe(
filter(u => u.role === 'admin'), // 筛选管理员
sortBy('age'), // 按年龄排序
map(u => u.name.toUpperCase()), // 获取大写名字
take(2) // 取前两个
);
console.log(getTopAdminNames(users)); // ['ALICE', 'CHARLIE']
6.3 异步管道
// 支持异步函数的管道
const pipeAsync = (...fns) => initialValue =>
fns.reduce(
(promise, fn) => promise.then(fn),
Promise.resolve(initialValue)
);
// 模拟异步操作
const fetchUser = async (id) => {
console.log(`获取用户 ${id}...`);
return { id, name: 'Alice', email: 'alice@example.com' };
};
const fetchUserPosts = async (user) => {
console.log(`获取 ${user.name} 的帖子...`);
return {
...user,
posts: ['Post 1', 'Post 2', 'Post 3']
};
};
const formatUserData = async (data) => {
console.log('格式化数据...');
return {
displayName: data.name.toUpperCase(),
email: data.email,
postCount: data.posts.length
};
};
const processUser = pipeAsync(
fetchUser,
fetchUserPosts,
formatUserData
);
processUser(1).then(console.log);
// 获取用户 1...
// 获取 Alice 的帖子...
// 格式化数据...
// { displayName: 'ALICE', email: 'alice@example.com', postCount: 3 }
6.4 条件组合
// 创建条件执行函数
const when = (predicate, fn) => x => predicate(x) ? fn(x) : x;
const unless = (predicate, fn) => x => predicate(x) ? x : fn(x);
const isEven = x => x % 2 === 0;
const double = x => x * 2;
const addOne = x => x + 1;
const doubleIfEven = when(isEven, double);
console.log(doubleIfEven(4)); // 8
console.log(doubleIfEven(5)); // 5
const addOneIfOdd = unless(isEven, addOne);
console.log(addOneIfOdd(4)); // 4
console.log(addOneIfOdd(5)); // 6
// 分支组合
const ifElse = (predicate, onTrue, onFalse) => x =>
predicate(x) ? onTrue(x) : onFalse(x);
const processNumber = ifElse(
isEven,
x => `${x} 是偶数,乘2得 ${x * 2}`,
x => `${x} 是奇数,加1得 ${x + 1}`
);
console.log(processNumber(4)); // "4 是偶数,乘2得 8"
console.log(processNumber(5)); // "5 是奇数,加1得 6"
// 多条件分支
const cond = (...pairs) => x => {
for (const [predicate, fn] of pairs) {
if (predicate(x)) {
return fn(x);
}
}
return x;
};
const classifyAge = cond(
[age => age < 13, () => '儿童'],
[age => age < 20, () => '青少年'],
[age => age < 60, () => '成年人'],
[() => true, () => '老年人']
);
console.log(classifyAge(8)); // "儿童"
console.log(classifyAge(15)); // "青少年"
console.log(classifyAge(30)); // "成年人"
console.log(classifyAge(70)); // "老年人"
7. 柯里化与偏应用
7.1 柯里化 (Currying)
柯里化是将一个接受多个参数的函数转换为一系列接受单个参数的函数。
// 普通函数
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
// 手动柯里化
function addCurried(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(addCurried(1)(2)(3)); // 6
// 箭头函数版本
const addCurriedArrow = a => b => c => a + b + c;
console.log(addCurriedArrow(1)(2)(3)); // 6
// 部分应用
const add1 = addCurriedArrow(1);
const add1and2 = add1(2);
console.log(add1and2(3)); // 6
console.log(add1and2(10)); // 13
// 通用柯里化函数
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, [...args, ...moreArgs]);
};
};
}
// 使用示例
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24
console.log(curriedMultiply(2, 3, 4)); // 24
// 创建专用函数
const double = curriedMultiply(2)(1);
console.log(double(5)); // 10
console.log(double(10)); // 20
7.2 柯里化的实际应用
// 1. 配置化的API请求
const request = curry((method, baseUrl, endpoint, data) => {
console.log(`${method} ${baseUrl}${endpoint}`, data);
// 实际实现会发送真实请求
return { method, url: `${baseUrl}${endpoint}`, data };
});
const apiRequest = request('POST')('https://api.example.com');
const createUser = apiRequest('/users');
const createPost = apiRequest('/posts');
createUser({ name: 'Alice' }); // POST https://api.example.com/users { name: 'Alice' }
createPost({ title: 'Hello' }); // POST https://api.example.com/posts { title: 'Hello' }
// 2. 格式化函数
const formatCurrency = curry((symbol, decimals, amount) => {
return `${symbol}${amount.toFixed(decimals)}`;
});
const formatUSD = formatCurrency('$')(2);
const formatEUR = formatCurrency('€')(2);
const formatJPY = formatCurrency('¥')(0);
console.log(formatUSD(1234.5)); // "$1234.50"
console.log(formatEUR(1234.5)); // "€1234.50"
console.log(formatJPY(1234.5)); // "¥1235"
// 3. 事件处理
const handleEvent = curry((handler, eventName, element) => {
element.addEventListener(eventName, handler);
return () => element.removeEventListener(eventName, handler);
});
const logEvent = event => console.log('Event:', event.type);
const addClickHandler = handleEvent(logEvent)('click');
// addClickHandler(document.getElementById('myButton'));
// 4. 验证函数
const validate = curry((validator, errorMsg, value) => {
return validator(value)
? { valid: true, value }
: { valid: false, error: errorMsg };
});
const isNotEmpty = value => value && value.trim().length > 0;
const isEmail = value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
const minLength = min => value => value && value.length >= min;
const validateRequired = validate(isNotEmpty)('此字段必填');
const validateEmail = validate(isEmail)('请输入有效邮箱');
const validatePassword = validate(minLength(8))('密码至少8位');
console.log(validateRequired('')); // { valid: false, error: '此字段必填' }
console.log(validateRequired('hello')); // { valid: true, value: 'hello' }
console.log(validateEmail('test@a.com')); // { valid: true, value: 'test@a.com' }
console.log(validatePassword('123')); // { valid: false, error: '密码至少8位' }
7.3 偏应用 (Partial Application)
偏应用是固定函数的一部分参数,返回一个接受剩余参数的新函数。
// 简单的偏应用函数
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs);
};
}
function greet(greeting, punctuation, name) {
return `${greeting}, ${name}${punctuation}`;
}
const greetHello = partial(greet, 'Hello', '!');
console.log(greetHello('Alice')); // "Hello, Alice!"
console.log(greetHello('Bob')); // "Hello, Bob!"
const greetHi = partial(greet, 'Hi');
console.log(greetHi('?', 'Charlie')); // "Hi, Charlie?"
// 使用占位符的偏应用
const _ = Symbol('placeholder');
function partialWithPlaceholder(fn, ...presetArgs) {
return function(...laterArgs) {
let laterIndex = 0;
const args = presetArgs.map(arg =>
arg === _ ? laterArgs[laterIndex++] : arg
);
return fn(...args, ...laterArgs.slice(laterIndex));
};
}
function subtract(a, b) {
return a - b;
}
const subtractFrom10 = partialWithPlaceholder(subtract, 10, _);
console.log(subtractFrom10(3)); // 7
const subtract5 = partialWithPlaceholder(subtract, _, 5);
console.log(subtract5(10)); // 5
// 使用 bind 实现偏应用
function multiply(a, b, c) {
return a * b * c;
}
const multiplyBy2 = multiply.bind(null, 2);
console.log(multiplyBy2(3, 4)); // 24
const multiplyBy2And3 = multiply.bind(null, 2, 3);
console.log(multiplyBy2And3(4)); // 24
7.4 柯里化 vs 偏应用
// 柯里化:将 f(a, b, c) 转换为 f(a)(b)(c)
// 每次只接受一个参数
// 偏应用:固定部分参数,返回接受剩余参数的函数
// 可以一次固定多个参数
function example(a, b, c, d) {
return a + b + c + d;
}
// 柯里化后
const curriedExample = curry(example);
console.log(curriedExample(1)(2)(3)(4)); // 10
console.log(curriedExample(1, 2)(3)(4)); // 10 (这是增强版柯里化)
// 偏应用后
const partialExample = partial(example, 1, 2);
console.log(partialExample(3, 4)); // 10
// 柯里化的一步步调用
const step1 = curriedExample(1);
const step2 = step1(2);
const step3 = step2(3);
const result = step3(4);
console.log(result); // 10
8. 实战案例
8.1 数据处理管道
// 电商订单处理系统
const orders = [
{ id: 1, customer: 'Alice', items: [
{ name: 'iPhone', price: 999, quantity: 1 },
{ name: 'Case', price: 29, quantity: 2 }
], date: '2024-01-15', status: 'completed' },
{ id: 2, customer: 'Bob', items: [
{ name: 'MacBook', price: 1299, quantity: 1 }
], date: '2024-01-16', status: 'pending' },
{ id: 3, customer: 'Charlie', items: [
{ name: 'AirPods', price: 199, quantity: 2 },
{ name: 'Charger', price: 29, quantity: 1 }
], date: '2024-01-15', status: 'completed' },
{ id: 4, customer: 'Alice', items: [
{ name: 'iPad', price: 799, quantity: 1 }
], date: '2024-01-17', status: 'completed' }
];
// 工具函数
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
const curry = fn => function curried(...args) {
return args.length >= fn.length
? fn(...args)
: (...more) => curried(...args, ...more);
};
// 基础操作函数(柯里化)
const filter = curry((predicate, arr) => arr.filter(predicate));
const map = curry((fn, arr) => arr.map(fn));
const reduce = curry((reducer, initial, arr) => arr.reduce(reducer, initial));
const sortBy = curry((fn, arr) => [...arr].sort((a, b) => fn(a) - fn(b)));
const groupBy = curry((keyFn, arr) =>
arr.reduce((acc, item) => {
const key = keyFn(item);
acc[key] = acc[key] || [];
acc[key].push(item);
return acc;
}, {})
);
// 业务逻辑函数
const calculateOrderTotal = order => ({
...order,
total: order.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
});
const isCompleted = order => order.status === 'completed';
const isCustomer = curry((name, order) => order.customer === name);
// 1. 计算所有完成订单的总收入
const totalRevenue = pipe(
filter(isCompleted),
map(calculateOrderTotal),
reduce((sum, order) => sum + order.total, 0)
)(orders);
console.log('总收入:', totalRevenue); // 2853
// 2. 获取某个客户的订单统计
const getCustomerStats = customerName => pipe(
filter(isCustomer(customerName)),
map(calculateOrderTotal),
orders => ({
customer: customerName,
orderCount: orders.length,
totalSpent: orders.reduce((sum, o) => sum + o.total, 0),
averageOrder: orders.length > 0
? orders.reduce((sum, o) => sum + o.total, 0) / orders.length
: 0
})
)(orders);
console.log('Alice的统计:', getCustomerStats('Alice'));
// { customer: 'Alice', orderCount: 2, totalSpent: 1856, averageOrder: 928 }
// 3. 按日期分组的订单报告
const ordersByDate = pipe(
map(calculateOrderTotal),
groupBy(order => order.date),
Object.entries,
map(([date, orders]) => ({
date,
orderCount: orders.length,
totalRevenue: orders.reduce((sum, o) => sum + o.total, 0)
})),
sortBy(report => new Date(report.date))
)(orders);
console.log('按日期分组:');
ordersByDate.forEach(report => {
console.log(` ${report.date}: ${report.orderCount}单, $${report.totalRevenue}`);
});
// 4. 热销商品排行
const topProducts = pipe(
flatMap => orders.flatMap(o => o.items),
reduce((acc, item) => {
acc[item.name] = (acc[item.name] || 0) + item.quantity;
return acc;
}, {}),
Object.entries,
map(([name, quantity]) => ({ name, quantity })),
arr => arr.sort((a, b) => b.quantity - a.quantity)
)(orders);
console.log('热销商品:', topProducts);
8.2 表单验证系统
// 函数式表单验证
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
const curry = fn => (...args) =>
args.length >= fn.length ? fn(...args) : curry(fn.bind(null, ...args));
// 验证结果类型
const Success = value => ({
isSuccess: true,
value,
map: fn => Success(fn(value)),
flatMap: fn => fn(value),
getOrElse: () => value,
fold: (onError, onSuccess) => onSuccess(value)
});
const Failure = errors => ({
isSuccess: false,
errors,
map: () => Failure(errors),
flatMap: () => Failure(errors),
getOrElse: defaultValue => defaultValue,
fold: (onError, onSuccess) => onError(errors)
});
// 基础验证器
const createValidator = curry((predicate, errorMessage, value) =>
predicate(value) ? Success(value) : Failure([errorMessage])
);
// 组合验证器
const combineValidators = (...validators) => value => {
const results = validators.map(v => v(value));
const errors = results
.filter(r => !r.isSuccess)
.flatMap(r => r.errors);
return errors.length === 0 ? Success(value) : Failure(errors);
};
// 具体验证器
const isRequired = createValidator(
v => v !== null && v !== undefined && v !== '',
'此字段必填'
);
const isEmail = createValidator(
v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
'请输入有效的邮箱地址'
);
const minLength = min => createValidator(
v => v && v.length >= min,
`长度至少为 ${min} 个字符`
);
const maxLength = max => createValidator(
v => !v || v.length <= max,
`长度不能超过 ${max} 个字符`
);
const isNumber = createValidator(
v => !isNaN(Number(v)),
'必须是数字'
);
const inRange = (min, max) => createValidator(
v => {
const num = Number(v);
return num >= min && num <= max;
},
`必须在 ${min} 到 ${max} 之间`
);
const matches = regex => createValidator(
v => regex.test(v),
'格式不正确'
);
// 验证整个表单
const validateField = (fieldName, value, ...validators) => {
const result = combineValidators(...validators)(value);
return result.fold(
errors => ({ [fieldName]: { valid: false, errors } }),
value => ({ [fieldName]: { valid: true, value } })
);
};
const validateForm = schema => formData => {
const results = Object.entries(schema).map(([field, validators]) =>
validateField(field, formData[field], ...validators)
);
const merged = results.reduce((acc, r) => ({ ...acc, ...r }), {});
const isValid = Object.values(merged).every(r => r.valid);
return { isValid, fields: merged };
};
// 使用示例
const userFormSchema = {
username: [isRequired, minLength(3), maxLength(20)],
email: [isRequired, isEmail],
age: [isRequired, isNumber, inRange(18, 120)],
password: [isRequired, minLength(8), matches(/[A-Z]/), matches(/[0-9]/)]
};
const validateUserForm = validateForm(userFormSchema);
// 测试有效数据
const validData = {
username: 'johndoe',
email: 'john@example.com',
age: '25',
password: 'SecurePass123'
};
console.log('有效数据验证:');
console.log(validateUserForm(validData));
// { isValid: true, fields: { username: { valid: true, value: 'johndoe' }, ... } }
// 测试无效数据
const invalidData = {
username: 'ab',
email: 'invalid-email',
age: '15',
password: 'weak'
};
console.log('\n无效数据验证:');
const result = validateUserForm(invalidData);
console.log('isValid:', result.isValid);
Object.entries(result.fields).forEach(([field, data]) => {
if (!data.valid) {
console.log(` ${field}: ${data.errors.join(', ')}`);
}
});
8.3 状态管理
// 简易的函数式状态管理
const createStore = (reducer, initialState) => {
let state = initialState;
const listeners = [];
return {
getState: () => state,
dispatch: action => {
state = reducer(state, action);
listeners.forEach(listener => listener(state));
return action;
},
subscribe: listener => {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
};
}
};
};
// Action creators
const createAction = type => payload => ({ type, payload });
const actions = {
addTodo: createAction('ADD_TODO'),
toggleTodo: createAction('TOGGLE_TODO'),
removeTodo: createAction('REMOVE_TODO'),
setFilter: createAction('SET_FILTER')
};
// Reducer(纯函数)
const initialState = {
todos: [],
filter: 'all', // 'all', 'active', 'completed'
nextId: 1
};
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [
...state.todos,
{ id: state.nextId, text: action.payload, completed: false }
],
nextId: state.nextId + 1
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'REMOVE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case 'SET_FILTER':
return {
...state,
filter: action.payload
};
default:
return state;
}
};
// Selectors(派生状态)
const selectTodos = state => state.todos;
const selectFilter = state => state.filter;
const selectFilteredTodos = state => {
const todos = selectTodos(state);
const filter = selectFilter(state);
switch (filter) {
case 'active':
return todos.filter(t => !t.completed);
case 'completed':
return todos.filter(t => t.completed);
default:
return todos;
}
};
const selectStats = state => {
const todos = selectTodos(state);
return {
total: todos.length,
completed: todos.filter(t => t.completed).length,
active: todos.filter(t => !t.completed).length
};
};
// 使用
const store = createStore(todoReducer, initialState);
// 订阅状态变化
const unsubscribe = store.subscribe(state => {
console.log('\n当前状态:');
console.log('Todos:', selectFilteredTodos(state));
console.log('统计:', selectStats(state));
});
// 派发 actions
console.log('=== 添加待办事项 ===');
store.dispatch(actions.addTodo('学习函数式编程'));
store.dispatch(actions.addTodo('写代码'));
store.dispatch(actions.addTodo('看文档'));
console.log('\n=== 完成一项 ===');
store.dispatch(actions.toggleTodo(1));
console.log('\n=== 设置过滤器为 active ===');
store.dispatch(actions.setFilter('active'));
console.log('\n=== 删除一项 ===');
store.dispatch(actions.removeTodo(2));
// 取消订阅
unsubscribe();
8.4 函数式工具库
// 创建一个小型函数式工具库
const FP = {
// 核心函数
pipe: (...fns) => x => fns.reduce((acc, fn) => fn(acc), x),
compose: (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x),
curry: fn => {
const curried = (...args) =>
args.length >= fn.length
? fn(...args)
: (...more) => curried(...args, ...more);
return curried;
},
// 数组操作
map: fn => arr => arr.map(fn),
filter: predicate => arr => arr.filter(predicate),
reduce: (fn, initial) => arr => arr.reduce(fn, initial),
find: predicate => arr => arr.find(predicate),
some: predicate => arr => arr.some(predicate),
every: predicate => arr => arr.every(predicate),
// 对象操作
prop: key => obj => obj[key],
assoc: (key, value) => obj => ({ ...obj, [key]: value }),
omit: keys => obj => {
const result = { ...obj };
keys.forEach(key => delete result[key]);
return result;
},
pick: keys => obj =>
keys.reduce((acc, key) => {
if (key in obj) acc[key] = obj[key];
return acc;
}, {}),
// 逻辑操作
not: fn => (...args) => !fn(...args),
and: (f, g) => (...args) => f(...args) && g(...args),
or: (f, g) => (...args) => f(...args) || g(...args),
// 条件操作
when: (predicate, fn) => x => predicate(x) ? fn(x) : x,
unless: (predicate, fn) => x => predicate(x) ? x : fn(x),
ifElse: (predicate, onTrue, onFalse) => x =>
predicate(x) ? onTrue(x) : onFalse(x),
// 比较操作
equals: a => b => a === b,
gt: a => b => b > a,
gte: a => b => b >= a,
lt: a => b => b < a,
lte: a => b => b <= a,
// 数学操作
add: a => b => a + b,
subtract: a => b => b - a,
multiply: a => b => a * b,
divide: a => b => b / a,
// 字符串操作
split: separator => str => str.split(separator),
join: separator => arr => arr.join(separator),
trim: str => str.trim(),
toLowerCase: str => str.toLowerCase(),
toUpperCase: str => str.toUpperCase(),
// 实用工具
identity: x => x,
constant: x => () => x,
tap: fn => x => { fn(x); return x; },
// 数组工具
head: arr => arr[0],
tail: arr => arr.slice(1),
last: arr => arr[arr.length - 1],
init: arr => arr.slice(0, -1),
take: n => arr => arr.slice(0, n),
drop: n => arr => arr.slice(n),
// 分组和排序
groupBy: keyFn => arr => arr.reduce((acc, item) => {
const key = keyFn(item);
acc[key] = acc[key] || [];
acc[key].push(item);
return acc;
}, {}),
sortBy: fn => arr => [...arr].sort((a, b) => {
const va = fn(a), vb = fn(b);
return va < vb ? -1 : va > vb ? 1 : 0;
}),
// 去重
uniq: arr => [...new Set(arr)],
uniqBy: fn => arr => {
const seen = new Set();
return arr.filter(item => {
const key = fn(item);
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
};
// 使用示例
const { pipe, map, filter, reduce, prop, sortBy, take, groupBy } = FP;
const users = [
{ name: 'Alice', age: 25, department: 'Engineering' },
{ name: 'Bob', age: 30, department: 'Marketing' },
{ name: 'Charlie', age: 35, department: 'Engineering' },
{ name: 'David', age: 28, department: 'Sales' },
{ name: 'Eve', age: 32, department: 'Engineering' }
];
// 获取工程部门年龄最大的两个人的名字
const result = pipe(
filter(user => user.department === 'Engineering'),
sortBy(prop('age')),
arr => arr.reverse(),
take(2),
map(prop('name'))
)(users);
console.log(result); // ['Charlie', 'Eve']
// 按部门统计平均年龄
const avgAgeByDept = pipe(
groupBy(prop('department')),
Object.entries,
map(([dept, members]) => ({
department: dept,
avgAge: members.reduce((sum, m) => sum + m.age, 0) / members.length,
count: members.length
})),
sortBy(prop('avgAge'))
)(users);
console.log(avgAgeByDept);
📖 总结
函数式编程的核心原则
- 使用纯函数 - 相同输入总是产生相同输出,无副作用
- 保持数据不可变 - 创建新数据而不是修改现有数据
- 使用高阶函数 - 函数可以作为参数传递和返回
- 函数组合 - 将简单函数组合成复杂函数
- 声明式编程 - 描述"做什么"而不是"怎么做"
学习路径建议
1. 基础阶段
├── 理解纯函数概念
├── 掌握 map、filter、reduce
└── 理解不可变性
2. 进阶阶段
├── 学习高阶函数模式
├── 掌握函数组合和管道
└── 理解柯里化和偏应用
3. 实践阶段
├── 在项目中应用 FP 原则
├── 使用 FP 库(Ramda、Lodash/fp)
└── 构建自己的工具函数库
推荐资源
- 《JavaScript函数式编程指南》
- Ramda.js - 实用的函数式编程库
- Professor Frisby's Mostly Adequate Guide to FP
记住:函数式编程不是全有或全无的选择。你可以在现有代码中逐步引入函数式概念,慢慢体会它带来的好处!
0 个评论
要回复文章请先登录或注册