JavaScript 函数式编程指南 读书笔记
纯函数
纯函数:相同输入永远相同输出且无任何可观察的副作用。如 slice 和 splice,可以把 slice 看作是纯函数,而 splice 不是,因为 splice 修改原数组,违背了了相同输入不同输出的原则。
纯函数借鉴于数学中函数的概念,即一对一,而不是一对多(即相同输入永远相同输出)
纯函数的输出结果不应该依赖外部的环境,如:
// 不纯 const minimum = 21; const checkAge = function(age){ return age >= minimum } // 纯的 const checkAge = function(age) { const minimum = 21; return age >= minimum; }
很明显,对于不纯的函数的实现, minimum
变量在外部环境中,而这个变量随时都可能被更改。
在 JavaScript 中,我们可以使用 Object.freeze 方法将一个变量变为不可变对象,这样状态不会变化,也就保持了其 纯粹性。
const immutableState = Object.freeze({ minimum: 21 }); immutableState.minimumu; // 21 immutableState.minimumu = 32; // 严格模式下将会报错 immutableState.minimumu; // 21
副作用是在计算结果的过程中,系统状态的一种改变,或者与外部世界进行的可观察的交互。
包括不限于:
- 更改文件系统
- 更改数据库记录
- 发送请求
- 改变外部数据
- DOM 查询
- 访问系统状态
只要是 跟外部环境发生的交互 都是副作用,面对副作用,不是要完全禁止,而是应该让这些副作用变得 可控。
纯函数的好处
可缓存
对输入进行缓存(得益于一对一原则)
const squareNumber = memoize(function(x){ return x * x; }) squareNumber(4); // 16 首次计算 squareNumber(4); // 16 从缓存中直接读取结果
一个简单的记忆函数实现:
const memoize = function(f) { const cache = {}; return function(){ const arg_str = JSON.stringify(arguments); cache[arg_str] = cache[arg_str] || f.apply(f, arguments); return cache[arg_str]; } }
可移植性
可移植性可以意味着把函数序列化(serializing)并通过 socket 发送。也可以意味着代码能够在 web workers 中运行。总之,可移植性是一个非常强大的特性
可测试性
只需要简单的输入,然后对输出断言即可。
合理性
相同输入永远相同输出。
引用透明性 :如果一段代码的 执行结果 可以 替换 成这段 代码,并且不改变整个程序的行为,那么这段代码是引用透明的。
纯函数允许我们并行执行任意的纯函数,因为不需要访问共享的内存,也就不会进入竞争态。对于JavaScript来说服务端的Nodejs环境与 web worker 的浏览器都可以实现。
柯里化(curry)
概念:把接受多个参数的函数转化为接受一个单一参数的(最初函数的第一个参数)的函数,并且返回接受余下参数并且返回结果的新函数的技术。
const add = function(x) { return function(y) { return x + y; } } const addTen = add(10); addTen(2); // 12
很明显,上文柯里化的秘密在于闭包。
使用 lodash curry 方法的例子:
const curry = require("lodash").curry; const match = curry(function(what, str){ return str.match(what); }); const replace = curry(function(what, replacement, str){ return str.replace(what, replacement); })
使用:
match(/s+/g, "hello world"); // [" "] match(/s+/g)("hello world"); // [" "] const hasSpaces = match(/s+/g); hasSpaces("hello world"); // [" "]
可想而知,这种看似“预加载”的能力,在大型项目中使用可以大大简化代码。
柯里化非常符合纯函数的定义,一个输入准确对应于一个输出,当然为了减少()的调用,curry函数同样可以一次传递多个参数。
代码组合(compose)
一个简单的例子:
const compose = function(f, g) { return function(x){ return f(g(x)); } }
使用 compose
将字母转大写与末尾添加 !
的两个函数组合,如此一来两个函数产生的奇妙的变化,两个函数的 魔法 的作用将组成起一个崭新的函数。
const toUpperCase = function(x) { return x.toUpperCase(); }; const exclaim = function(x) { return x + '!'; }; cosnt shout = compose(exclaim, toUpperCase); shout("good student"); // "GOOD STUDENT!"
代码对于 compose 函数来说是 右参数 先与 左参数 执行,这样就是一个 从右到左 的数据流。
一个顺序很重要的例子:
const head = function(x){ return x[0]; } const reverse = reduce(function(acc, x) { return [x].concat(acc); }, []); const last = compose(head, reverse);
从右向左执行更加反映数学上的含义。而且实际上,所有的组合都有一个 共性 就是 符合结合律。
const associative = compose(f, compose(g, h)) == compose(compose(f,g), h);
既然符合结合律,也就意味着调用分组并不重要,也就是说可以无关参数顺序了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: at-ui 源码阅读
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论