概念:
副作用是在计算结果的过程中, 系统状态的一种变化, 或者与外部世界进行的可观察的交互.
上文中 https://github.com/zhuanyongxigua/blog/issues/16 的纯函数的概念很严格, 这个副作用的概念也是. 它的要求很高, 概括的讲, 只要是跟函数外部环境发生的交互就都是副作用. 从 "副作用" 这个词语来看, 它更多的情况在于 "改变系统状态".
在教程 https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch3.html#追求“纯”的理由 中列举的一些副作用:
更改文件系统
往数据库插入记录
发送一个 http 请求
可变数据
打印 / log
获取用户输入
DOM 查询
访问系统状态
如果完全没有副作用, 那我们的代码就是单纯的跑一遍浪费了一点电而已, 除此之外什么都没有发生, 这样的话我们写代码就没有意义了. 所以, 在 JS 中, 我们的目的不是完全消除副作用注 1, 而是避免那些不应该出现的副作用.
JS 原生的方法中, map 就很函数式, 他会返回一个新的数组, 不会改变原数组. 而 pop 这种方法就很不好, 它在操作了数组之后, 也改变数组本身.
所以当我们要使用那些有副作用的方法写纯函数的时候, 记得做一次深拷贝:
例 1
- const myPop = x => {let [...y] = x;
- return y.pop();
- }
使用一个固定的共享状态或者调用一个纯函数不算是副作用, 例子如下:
例 2
- const a = 5;
- function A(b) {
- return a + b;
- }
- A(5);
调用纯函数的例子:
例 3
- function foo(x) {
- return bar(x);
- }
- function bar(y) {
- return y + 1;
- }
- foo(1);
虽然不算是副作用, 可更加推荐的方式是把函数 bar 用参数的方式传进来, 这样就做到了解耦, 用起来更加的方便:
例 4
- function foo(fn, x) {
- return fn(x);
- }
- function bar(y) {
- return y + 1;
- }
- foo(bar, 1);
如果使用柯里化的方式, 会更加的清爽和方便:
例 5
- function foo(fn) {
- return function(x) {
- return fn(x);
- }
- }
- function bar(y) {
- return y + 1;
- }
- foo(bar)(1);
这个例子依然存在一个会令我们感到不安的地方, 那就是 bar 可能会被修改. 例如:
例 6
- function foo(fn, x) {
- return fn(x);
- }
- function bar(y) {
- return y + 1;
- }
- bar = undefined;
- foo(bar, 1);
当然我们平时很少会大脑抽筋在全局作用域下写出一个 bar = undefined 来让我们的系统出错, 这更可能在某个有副作用的函数内出现这种情况. 这就是为什么我们要避免副作用. 这个情况在 ES6 中会得到改善, 例如:
例 7
- const foo = function(fn, x) {
- return fn(x);
- }
- const bar = function(y) {
- return y + 1;
- }
- bar = undefined; // error
- foo(bar, 1);
个人建议用 const 的方式, 这样更加的安全, 即便出错也可以快速定位.
注释:
注 1: 如果继续深入学习, 对与上面列出的一些副作用, 函数式还有一种延迟执行的方式 (IO 容器) 来使这些操作变纯.
参考资料:
JS 函数式编程指南 https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/
Functional-Light JavaScript https://github.com/getify/Functional-Light-JS
来源: https://juejin.im/post/5b22071af265da59a23f14ba