接续上文:
https://blog.csdn.net/m0_73589512?type=blog
https://blog.csdn.net/m0_73589512?type=blog如果本篇文章对您有帮助,请留下你的小艾心哟
目录
[深入 JavaScript 函数式编程:从基础到实战(含面试题解析)](#深入 JavaScript 函数式编程:从基础到实战(含面试题解析))
[一、为什么需要函数式编程?------ 三大编程范式对比](#一、为什么需要函数式编程?—— 三大编程范式对比)
[1. 命令式写法(关注步骤)](#1. 命令式写法(关注步骤))
[2. 函数式写法(关注结果)](#2. 函数式写法(关注结果))
[2.1 纯函数:可预测性的基石](#2.1 纯函数:可预测性的基石)
[2.2 不可变数据:状态管理的核心](#2.2 不可变数据:状态管理的核心)
[2.3 高阶函数:函数的 "函数"](#2.3 高阶函数:函数的 “函数”)
[2.4 函数组合:管道式编程](#2.4 函数组合:管道式编程)
[2.5 柯里化:函数的 "拆分与复用"](#2.5 柯里化:函数的 “拆分与复用”)
[实战:API 请求复用](#实战:API 请求复用)
[2.6 函子(Functor):容器化编程](#2.6 函子(Functor):容器化编程)
[实战:Maybe 函子(安全处理空值)](#实战:Maybe 函子(安全处理空值))
[3.1 手写柯里化函数(支持add(1)(2)(3)() = 6)](#3.1 手写柯里化函数(支持add(1)(2)(3)() = 6))
[3.2 手写函数组合pipe工具](#3.2 手写函数组合pipe工具)
[3.3 用函数式思想实现数组去重](#3.3 用函数式思想实现数组去重)
[3.4 解释 React 组件为什么要设计为纯函数](#3.4 解释 React 组件为什么要设计为纯函数)
深入 JavaScript 函数式编程:从基础到实战(含面试题解析)
函数式编程 是现代前端开发的核心范式之一,它以 "纯函数""不可变数据""函数组合" 为核心 ,让代码更可预测、易测试、易维护。无论是 React、Redux 等框架的设计理念,还是日常业务中的数据处理,都离不开函数式编程的思想。本文将从编程范式对比入手,拆解函数式编程的核心概念、实战应用和面试高频题,帮你彻底掌握这一重要技能。
一、为什么需要函数式编程?------ 三大编程范式对比
要理解函数式编程的价值,首先要明确它与传统编程范式的区别:
| 编程范式 | 核心关注点 | 代码示例 | 特点 |
|---|---|---|---|
| 命令式编程 | 怎么做(步骤) | 用for循环遍历数组,手动累加求和 |
步骤繁琐,易出错,难维护 |
| 面向对象编程 | 谁来做(对象) | 定义Calculator类,通过实例方法求和 |
封装性强,但状态管理复杂 |
| 函数式编程 | 做什么(结果) | 用reduce方法直接声明求和逻辑 |
声明式写法,简洁清晰,可预测 |
直观对比:数据处理场景
假设需要筛选活跃且年龄 > 20 的用户,提取姓名并转为大写,最后排序:
const users = [
{ name: 'Alice', age: 25, active: true },
{ name: 'Bob', age: 30, active: false },
{ name: 'Charlie', age: 35, active: true }
];
1. 命令式写法(关注步骤)
const result = [];
for (let i = 0; i < users.length; i++) {
if (users[i].active && users[i].age > 20) {
result.push(users[i].name.toUpperCase());
}
}
result.sort();
2. 函数式写法(关注结果)
const result = users
.filter(user => user.active && user.age > 20)
.map(user => user.name.toUpperCase())
.sort();
函数式编程的核心优势的是:屏蔽实现细节,聚焦业务逻辑,让代码更易读、易调试、易复用。
二、函数式编程核心概念
2.1 纯函数:可预测性的基石
纯函数是函数式编程的核心,定义为:相同输入永远返回相同输出,无副作用。
核心特征
-
无副作用:不修改外部状态(如全局变量、参数对象)、不与外部交互(如网络请求、打印日志);
-
幂等性:多次调用结果一致,不依赖外部环境。
示例对比
// 纯函数(推荐)
const add = (a, b) => a + b;
const toUpperCase = str => str.toUpperCase();
// 非纯函数(避免)
let counter = 0;
const increment = () => counter++; // 修改外部变量(副作用)
const getRandom = () => Math.random(); // 输入相同,输出不同
const log = (msg) => console.log(msg); // 与外部交互(副作用)
实际应用
Redux 的 reducer、React 的纯组件,本质都是纯函数:
// Redux reducer(纯函数)
const todoReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
// 返回新数组,不修改原state(不可变性)
return [...state, { id: action.id, text: action.text, completed: false }];
default:
return state;
}
};
2.2 不可变数据:状态管理的核心
不可变数据是指数据创建后不能直接修改,修改时需返回新数据。这是避免副作用、保证纯函数可预测性的关键。
示例对比
// 可变操作(危险)
const user = { name: 'Alice', age: 25 };
user.age = 26; // 直接修改原对象
// 不可变操作(安全)
const updatedUser = { ...user, age: 26 }; // 解构创建新对象
// 数组不可变操作
const numbers = [1, 2, 3];
// 错误:直接修改原数组
numbers.push(4);
// 正确:返回新数组
const newNumbers = [...numbers, 4]; // 新增
const filteredNumbers = numbers.filter(n => n > 1); // 过滤
const mappedNumbers = numbers.map(n => n * 2); // 转换
框架应用
React 中修改 state 必须遵循不可变性,否则无法触发重渲染:
// 函数式组件 + Hooks
const [users, setUsers] = useState([]);
const addUser = newUser => {
// 正确:返回新数组,不修改原state
setUsers(prev => [...prev, newUser]);
};
2.3 高阶函数:函数的 "函数"
高阶函数是指接收函数作为参数,或返回函数作为结果的函数,是函数式编程的重要工具。
常见示例
-
数组原生方法:
map、filter、reduce、forEach; -
自定义高阶函数:日志装饰器、防抖节流函数。
实战:日志装饰器
// 高阶函数:为目标函数添加日志功能
const withLogging = (fn) => (...args) => {
console.log(`调用函数 ${fn.name},参数:`, args);
const result = fn(...args);
console.log(`函数 ${fn.name} 结果:`, result);
return result;
};
// 使用
const add = (a, b) => a + b;
const safeAdd = withLogging(add);
safeAdd(2, 3);
// 输出:
// 调用函数 add,参数: [2, 3]
// 函数 add 结果: 5
2.4 函数组合:管道式编程
函数组合是将多个小函数组合成一个复杂函数,核心思想是 "一个函数的输出是另一个函数的输入",类似管道流水线。
核心工具
-
compose:从右到左执行函数(compose(f, g)(x) = f(g(x))); -
pipe:从左到右执行函数(更符合直觉,推荐使用)。
实战:用户数据处理管道
// 定义组合工具函数
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
// 原始用户数据
const users = [
{ name: ' alice ', age: '25' },
{ name: 'BOB', age: '30' }
];
// 步骤函数(小而纯)
const trimName = user => ({ ...user, name: user.name.trim() });
const capitalizeName = user => ({
...user,
name: user.name.charAt(0).toUpperCase() + user.name.slice(1).toLowerCase()
});
const parseAge = user => ({ ...user, age: parseInt(user.age) });
const isAdult = user => user.age >= 18;
// 组合成处理管道
const processUser = pipe(trimName, capitalizeName, parseAge);
// 执行管道
const adultUsers = users.map(processUser).filter(isAdult);
console.log(adultUsers);
// 输出:[{ name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }]
2.5 柯里化:函数的 "拆分与复用"
柯里化是将多参数函数转换为单参数函数链的技术,核心价值是 "参数复用" 和 "延迟执行"。
示例对比
// 普通多参数函数
const add = (a, b, c) => a + b + c;
add(1, 2, 3); // 6
// 手动柯里化
const curriedAdd = a => b => c => a + b + c;
curriedAdd(1)(2)(3); // 6
// 自动柯里化工具函数
const curry = (fn) => {
return function curried(...args) {
// 当参数数量满足原函数需求时,执行原函数
if (args.length >= fn.length) {
return fn(...args);
}
// 否则返回新函数,接收更多参数
return (...moreArgs) => curried(...args, moreArgs);
};
};
// 使用工具函数柯里化
const curriedMultiply = curry((a, b, c) => a * b * c);
curriedMultiply(2)(3)(4); // 24
curriedMultiply(2, 3)(4); // 24(支持灵活传参)
实战:API 请求复用
// 柯里化API请求函数
const apiRequest = (baseURL) => (endpoint) => (data) =>
fetch(`${baseURL}${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
});
// 1. 复用基础URL
const myAPI = apiRequest('https://api.example.com');
// 2. 复用端点
const login = myAPI('/login');
const register = myAPI('/register');
// 3. 传入具体数据
login({ username: 'alice', password: '123' });
register({ email: 'bob@example.com', password: '456' });
2.6 函子(Functor):容器化编程
函子是函数式编程的高级概念,本质是 "包含值的容器 ",且实现了map方法,支持对容器内的值进行安全转换。
核心作用
-
安全处理可能为
null/undefined的值; -
保持数据结构不变,支持链式调用。
实战:Maybe 函子(安全处理空值)
class Maybe {
constructor(value) {
this.value = value;
}
// 静态方法:创建Maybe实例
static of(value) {
return new Maybe(value);
}
// map方法:安全转换值(null时不执行转换)
map(fn) {
return this.value == null ? Maybe.of(null) : Maybe.of(fn(this.value));
}
// 提取容器内的值
fold() {
return this.value;
}
}
// 安全处理可能存在空值的用户数据
const user = { profile: { name: 'Alice' } };
// 模拟异常场景:const user = { profile: null };
const userName = Maybe.of(user)
.map(u => u.profile) // 若profile为null,后续map不执行
.map(p => p.name)
.map(name => name.toUpperCase())
.fold();
console.log(userName); // "ALICE"(无异常)或 null(有异常,不报错)
三、函数式编程面试高频题解析
3.1 手写柯里化函数(支持add(1)(2)(3)() = 6)
function add(a) {
// 闭包保留累加结果
let total = a;
function curried(b) {
// 若未传参(b为undefined),返回最终结果
if (b === undefined) {
return total;
}
// 累加参数,返回自身(支持链式调用)
total += b;
return curried;
}
return curried;
}
// 测试
console.log(add(1)(2)(3)()); // 6
console.log(add(1)(2)(3)(4)()); // 10
console.log(add(5)()); // 5
3.2 手写函数组合pipe工具
const pipe = (...fns) => {
// 边界处理:若未传函数,返回原值
if (fns.length === 0) return x => x;
// 若只传一个函数,直接返回该函数
if (fns.length === 1) return fns[0];
// 多函数组合:reduce依次执行,前一个函数的输出作为后一个的输入
return x => fns.reduce((acc, fn) => fn(acc), x);
};
// 测试
const double = x => x * 2;
const add1 = x => x + 1;
const square = x => x * x;
const process = pipe(double, add1, square);
console.log(process(3)); // ((3*2)+1)^2 = 49
3.3 用函数式思想实现数组去重
// 纯函数实现:不修改原数组,返回新数组
const uniqueArray = (arr) => {
// 利用Set去重,再转为数组(保持顺序)
return [...new Set(arr)];
};
// 进阶:支持对象数组去重(按指定key)
const uniqueObjectArray = (arr, key) => {
const map = new Map();
return arr.filter(item => {
const value = item[key];
if (!map.has(value)) {
map.set(value, true);
return true;
}
return false;
});
};
// 测试
const arr = [1, 2, 2, 3, 3, 3];
console.log(uniqueArray(arr)); // [1, 2, 3]
const objArr = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice' }
];
console.log(uniqueObjectArray(objArr, 'id')); // 按id去重
3.4 解释 React 组件为什么要设计为纯函数
标准答案:
-
纯函数 "相同输入(props)→ 相同输出(DOM)",保证组件渲染可预测,便于调试;
-
无副作用,避免组件内部修改外部状态,减少 bug;
-
便于 React 进行性能优化(如 React.memo 缓存组件渲染结果,避免不必要的重渲染);
-
符合函数式编程思想,组件更易复用、易测试。
四、总结
函数式编程的核心是 "用函数构建逻辑,保持数据不可变,避免副作用",它不是一套新的语法,而是一种新的编程思维:
-
从 "关注步骤" 转变为 "关注结果";
-
从 "修改状态" 转变为 "创建新状态";
-
从 "单一函数" 转变为 "函数组合"。
掌握函数式编程,不仅能写出更优雅、更健壮的代码,还能深刻理解 React、Redux 等框架的设计理念。建议在实际项目中多实践纯函数、函数组合、柯里化等技术,逐步将函数式思想融入日常开发。