blog
blog copied to clipboard
《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);
既然符合结合律,也就意味着调用分组并不重要,也就是说可以无关参数顺序了。