16. 纯函数、副作用与函数式编程初识 副本.md 来源:https://my.feishu.cn/file/B4cWbOP05oTpmDxaRsScZI5Ynzx 采集状态:已完成正文采集;原文件源码视图逐行采集,行号 1 至 89 连续,滚动到底。 ## 1. 核心概念 (Core Concept) * **纯函数 (Pure Function)**:指满足两个条件的函数:给定相同的输入,总是返回相同的输出;并且执行过程中没有任何可观察的副作用。 * **副作用 (Side Effect)**:指函数执行时,除了返回显而易见的计算结果外,还对外部世界产生了可观察的影响,例如修改全局变量、I/O 操作(读写文件、网络请求)、修改 DOM 等。 * **函数式编程 (Functional Programming)**:一种编程范式,强调使用纯函数、避免改变状态和可变数据,并使用函数作为一等公民。它将计算视为数学函数的求值。 ## 2. 为什么需要它? (The "Why") * **可预测性与可测试性 (Predictability & Testability):** 纯函数由于其确定的输入和输出,极大地提高了代码的可预测性,使得单元测试变得非常简单,只需测试输入与输出的关系即可。 * **并发与并行安全性 (Concurrency & Parallel Safety):** 纯函数不依赖或修改外部状态,天然适用于并发和并行环境,避免了多线程或多进程共享状态带来的复杂同步问题(如竞态条件)。 * **代码理解与推理 (Code Understanding & Reasoning):** 副作用使得代码的行为难以追踪,因为它的行为取决于外部状态。纯函数将复杂性局部化到函数内部,使得代码更容易理解和推导其行为。 ## 3. API 与用法 (API & Usage) 纯函数本身不是一个具体的 API,而是函数的一种属性或一种编写函数的方式。函数式编程也没有特定的内置 API(在通用编程语言如 JavaScript 中),它更多是一种编程风格和思想。 纯函数的例子(JavaScript): ```javascript // 纯函数:给定相同的输入 (a, b),总是返回相同的输出 (a + b),没有副作用。 function add(a, b) { return a + b; } // 非纯函数的例子 (含有副作用): // 例子 1: 依赖外部状态 let count = 0; function incrementAndGet() { count++; // 修改外部变量,副作用 return count; } // 例子 2: 修改传入的参数 (如果参数是引用类型) function modifyArray(arr) { arr.push(1); // 修改了函数外部的数组,副作用 return arr; } // 例子 3: 进行 I/O 操作 function logMessage(message) { console.log(message); // 写入到控制台,副作用 (尽管这是常见的操作) } // 例子 4: 读取外部状态 function getRandomNumber() { return Math.random(); // 依赖 Math 的内部状态(或者说依赖时间等外部因素),每次结果不同,非纯函数 } ``` 在函数式编程中,我们通常会结合使用高阶函数(Higher-Order Functions,如 `map`, `filter`, `reduce`)和纯函数来处理数据: ```javascript // 使用纯函数和高阶函数处理数组 const numbers = [1, 2, 3, 4, 5]; // 使用 map (高阶函数),传入一个纯函数 (num => num * 2) // map 本身是纯的,它返回一个新数组,不修改原数组 const doubledNumbers = numbers.map(num => num * 2); // [2, 4, 6, 8, 10] // 使用 filter (高阶函数),传入一个纯函数 (num => num > 2) // filter 本身是纯的,它返回一个新数组 const greaterThanTwo = numbers.filter(num => num > 2); // [3, 4, 5] // 使用 reduce (高阶函数),传入一个纯函数 ((acc, num) => acc + num) // reduce 本身是纯的,它返回一个单一值 const sum = numbers.reduce((acc, num) => acc + num, 0); // 15 console.log("Original:", numbers); // [1, 2, 3, 4, 5] - 原数组未被修改 console.log("Doubled:", doubledNumbers); console.log("Filtered:", greaterThanTwo); console.log("Sum:", sum); ``` ## 4. 关键注意事项 (Key Considerations) * **并非所有函数都能是纯函数:** 在实际应用中,完全没有副作用的程序是不可能的(比如最终需要将结果显示给用户,这就需要 I/O 副作用)。关键在于**控制副作用,将其隔离在程序的边缘**。 * **修改引用类型参数的陷阱:** JavaScript 中,Objects 和 Arrays 是通过引用传递的。如果函数修改了作为参数传入的引用类型对象或数组,就产生了副作用。为了保持纯净,应返回新的对象或数组,而不是修改原有的。可以使用展开运算符 `...`、`slice()`、`map()`、`filter()` 等方法创建副本。 * **理解状态管理的边界:** 在前端框架(如 React、Vue)中处理状态时,理解何时需要管理具有副作用的状态(如组件状态、全局状态)以及何时使用纯函数进行计算,是构建可维护应用的关键。许多现代状态管理库(如 Redux, Vuex, Zustand)都鼓励使用纯函数(如 Reducers)来更新状态。 * **性能与纯度折衷:** 虽然纯函数有诸多优点,但在极少数对性能有苛刻要求的场景下,为了避免创建大量新对象/数组,可能会选择有限度的修改,但这通常需要小心权衡和处理。 ## 5. 参考资料 (References) * **MDN Web Docs: 函数式编程 (Functional programming)**: [https://developer.mozilla.org/zh-CN/docs/Glossary/Functional_programming](https://developer.mozilla.org/zh-CN/docs/Glossary/Functional_programming) * **React Docs: Thinking in React (章节中隐含了纯函数的概念)**: [https://react.dev/learn/thinking-in-react](https://react.dev/learn/thinking-in-react) (React 组件应像纯函数一样,接收 props 作为输入,返回 JSX 作为输出,而不改变 props 或外部状态) * **维基百科: 纯函数**: [https://zh.wikipedia.org/wiki/%E7%B4%94%E5%87%BD%E6%95%B8](https://zh.wikipedia.org/wiki/%E7%B4%94%E5%87%BD%E6%95%B8) (提供形式化定义) * **维基百科: 副作用 (计算机科学)**: [https://zh.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8)](https://zh.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8)) --- 原文链接与附件索引 https://developer.mozilla.org/zh-CN/docs/Glossary/Functional_programming https://developer.mozilla.org/zh-CN/docs/Glossary/Functional_programming https://react.dev/learn/thinking-in-react https://react.dev/learn/thinking-in-react https://zh.wikipedia.org/wiki/%E7%B4%94%E5%87%BD%E6%95%B8 https://zh.wikipedia.org/wiki/%E7%B4%94%E5%87%BD%E6%95%B8 https://zh.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8) https://zh.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8