纯函数
纯函数简单总结就是:
- 相同的输入,得到相同的输出
- 函数在执行过程中,不能产生副作用
副作用的理解
副作用(side effect)其实是医学的一个概念,例如我们吃感冒药,虽然能治好感冒,但是会让我们感到头晕,或者比如吃去火的药,会让我们腹泻等等。
而在计算机科学中,副作用指的是:再执行一个函数时,除了返回函数值以外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储。纯函数案例
上面的就是一个纯函数,它可以保证我们只要传入的值相同,不论执行多少次结果都是一样的。 ```jsx let foo = 5;function sum(num1, num2) {
return num1 + num2;
}
function add(num) { return foo + num; }
console.log(add(5)); // 10 foo = 10; console.log(add(5)); // 15
上面的add函数不是纯函数,因为它内部有一个全局变量foo,当我们更改了foo的值,执行同样的add(5)结果和之前不一致。
<a name="CEF8v"></a>
## 纯函数的优势
纯函数可以保证函数的纯度,在编写时只要实现自己的业务逻辑即可,不需要关心依赖的其他变量是否发生改变。
<a name="ZRW0a"></a>
# 柯里化
柯里化是属于函数式编程的一个概念。<br />维基百科对于柯里化的定义:<br />把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受剩余参数,并且返回结果的新函数的技术。<br />柯里化声称“如果你固定某些参数,你将得到接受余下参数的一个函数”
维基百科的定义非常抽象,我们做一个总结:
- 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数
- 这个过程就称之为柯里化
```jsx
function add(x, y, z) {
return x + y + z;
}
var result = add(10, 20, 30);
console.log(result);
function sum1(x) {
return function (y) {
return function (z) {
return x + y + z;
};
};
}
var result1 = sum1(10)(20)(30);
console.log(result1);
// 简化柯里化的代码
var sum2 = (x) => (y) => (z) => {
return x + y + z;
};
console.log(sum2(10)(20)(30));
上面的sum1,sum2都是柯里化函数,sum2是sum1的简写形式。
柯里化-单一职责
为什么需要柯里化?
- 在函数式编程中,我们希望一个函数处理的问题尽可能单一,而不是将一大堆的处理过程交给一个函数来处理
- 那么我们是否可以将每次传入的参数在单一的函数中进行处理,处理完后在下一个函数中再使用处理后的结果
比如上面的案例,我们要求传入的函数分别进行如下处理:
- 第一个参数 + 2
- 第二个参数 * 2
- 第三个参数 ** 2
不使用柯里化,我们可以这样实现需求,但是实际开发中代码不会这么简单,会非常复杂,我们会尽可能让函数的代码简洁,这就需要用到柯里化 ```jsx function sum(x) { x = x + 2; return function (y) { y = y * 2; return function (z) {function add(x, y, z) {
x = x + 2;
y = y * 2;
z = z * z;
return x + y + z;
}
}; }; }z = z * z;
return x + y + z;
console.log(sum(10)(20)(30));
我们将一个函数拆分成3个函数,每个函数只处理自己负责的部分,最后实现结果和上面相同,也实现了函数的单一职责。
<a name="ry3Pz"></a>
## 柯里化-逻辑复用
假如在程序中,我们经常需要把5和另外一个数字进行相加,那么就可以用到柯里化的逻辑复用
```jsx
function sum(m, n) {
return m + n;
}
console.log(sum(5, 10));
console.log(sum(5, 14));
console.log(sum(5, 1100));
console.log(sum(5, 555));
如果是柯里化,则是这样使用
function makeAdder(count) {
return function (num) {
return count + num;
};
}
console.log(makeAdder(5)(10));
console.log(makeAdder(5)(14));
console.log(makeAdder(5)(1100));
console.log(makeAdder(5)(555));
这样和上面的看起来没有多大差别,看不出柯里化的作用,所以我们稍作改变
function makeAdder(count) {
return function (num) {
return count + num;
};
}
var adder5 = makeAdder(5);
console.log(adder5(10));
console.log(adder5(14));
console.log(adder5(1100));
console.log(adder5(555));
我们可以看到,adder5内部封装了“count = 5”的逻辑,只要调用adder5()函数,都是和5相加,实现了逻辑的复用。
这个函数我们的需求是能够打印日志,我们给它传入日期,状态,描述。
function log(date, type, message) {
console.log(
`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`
);
}
log(new Date(), "DEBUG", "查找到轮播图的bug");
log(new Date(), "DEBUG", "查询菜单的bug");
log(new Date(), "DEBUG", "查询数据的bug");
像上面的例子,我们的日期都是“new Date()”,所以可以将日期复用
var log = (date) => (type) => (message) => {
console.log(
`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`
);
};
// 如果我现在打印的都是当前时间
var nowLog = log(new Date());
nowLog("DEBUG")("查找到轮播图的bug");
nowLog("FETURE")("新增添加用户功能");
像这样就可以复用日期。
我们甚至可以将日期和错误类型一起复用
var nowAndDebugLog = log(new Date())("DEBUG");
nowAndDebugLog("查找到轮播图的bug");
nowAndDebugLog("查询菜单的bug");
nowAndDebugLog("查询数据的bug");
nowAndDebugLog将日期和错误类型一起封装,调用时只要传入错误信息就可以了。
自动柯里化函数的实现
自动柯里化函数就是把普通函数转换成柯里化函数。
function add1(x, y, z) {
return x + y + z;
}
function zxCurring(fn) {
function carried(...args) {
// 判断当前已经接收的参数的个数,和函数本身需要接收的参数是否已经一致了
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function (...args2) {
return carried.apply(this, [...args, ...args2]);
};
}
}
return carried;
}
var curryAdd = zxCurring(add1);
console.log(add1(10, 20, 30));
console.log(curryAdd(10)(20)(30));
console.log(curryAdd(10, 20)(30));
console.log(curryAdd(10, 20, 30));
其实原理并不复杂,简单来说就是判断+递归来实现。
具体分析:
以add1函数为例,“var curryAdd = zxCurring(add1);”就是把add1作为参数传递给zxCurring函数,zxCurring中的fn就是add1,然后因为我们希望curryAdd是一个函数,所以zxCurring函数的返回值是一个函数carried,然后我们可以通过“…args”来获取到curryAdd执行时的参数,并且我们可以通过fn.length也就是“函数名.length”的方式获取到add1函数需要接收的参数的长度,两个对比就能知道当前的柯里化后的函数是否获取到了做够多的参数,如果够了,那么执行fn,并且把args作为参数就可以了(这里需要注意一点,);如果不够,那么就执行else中的代码,返回一个函数,并且这个函数的参数…args2还能获取到curryAdd函数执行的下一次调用传递的参数,获取到下一次调用的参数以后,我们再将这一次和下一次调用的参数合并,再次调用carried函数,这时因为参数合并的原因,我们的args的length改变了,我们重新判断并重复刚才的步骤,直到最后合并的参数长度和原函数要求的参数长度一致,我们再通过fn.apply(this,args)执行,实现将函数转变为柯里化函数。
vue3中柯里化的应用和作用
组合函数
组合(Compose)函数是在JS开发过程中一种对函数的使用技巧、模式:
- 比如我们需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的
- 那么如果我们每次都需要进行两个函数的调用,操作上就显得很重复
- 我们是否可以将这两个函数组合起来,自动依次调用呢?
- 这个过程就是对函数的组合,我们称之为组合函数(Compose Function)
实现一个简单的组合函数:
function double(num) {
return num * 2;
}
function square(num) {
return num ** 2;
}
var count = 10;
var result = square(double(count));
console.log(result);
function composeFn(m, n) {
return function (count) {
return n(m(count));
};
}
var newFn = composeFn(double, square);
console.log(newFn(10));
这个组合函数就是把“double”和“square”传入,然后按顺序调用了一下。
接下来我们实现一个通用的组合函数:
function zxCompose(...fns) {
var length = fns.length;
for (var i = 0; i < length; i++) {
if (typeof fns[i] !== "function") {
throw new TypeError("Expect arguments are functions");
}
}
return (...args) => {
var index = 0;
var result = length ? fns[index].apply(this, args) : args;
while (++index < length) {
result = fns[index].call(this, result);
}
return result;
};
}