JS纯函数 柯里化函数 组合函数
2022-04-25 本文已影响0人
咸鱼不咸_123
1.纯函数(Pure Function)
函数式编程中有一个非常重要的概念叫做纯函数,javascript符合函数式编程的范式,所以也有纯函数的概念
- 在react开发中纯函数是被多次提及的
- 比如react中组件就被要求像是一个纯函数(为什么是像,因为还有class组件),redux中有一个reducer的概念,是要求必须是一个纯函数
- 所以掌握纯函数对于理解很多框架的设计是非常有帮助的
Vue3有一个setup函数——》编写很多其他的逻辑——》更加接近于原生开发——》函数的概念——》
1.1 维基百科的解释
在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数
-
此函数在相同的输入值,需产生相同的输出
-
函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
函数的输出只和输入值、函数内部的执行有关,和输入值以外的其他隐藏信息或状态无关,也和IO设备产生的输出无关
-
该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。
简单的总结
- 确定的输入,一定产生确定的输出
- 函数在执行过程中,不能产生副作用
1.1.1 相同的输入值,产生不同的输出
var name="abc"
function foo(){
return name;
}
foo(123)
name="123"
foo(123)
1.2 副作用的理解
副作用是什么?
- 副作用(side affect)其实是医学的一个概念,比如我们经常说吃什么药本来是为了治病,可能会产生一些其他的副作用
- 在计算机科学中,也引用了副作用的概念,表示在执行一个函数时,除了返回函数值以外,还对调用函数产生了附加的影响,比如修改了全局变量、修改参数或者修改外部的存储
- 纯函数在执行的过程中就是不能产生这样的副作用
- 副作用往往是产生bug的“温床”
后面编写js函数的时候,尽可能让函数只做一件事情,尽可能让函数的做的事情变的更简单
1.3 纯函数的练习
-
slice:slice截取数组不会对原数组进行任何操作,而是生成一个新的数组
-
splice:splice截取数组,会返回一个新的数组,原数组发生修改
-
slice就是一个纯函数,不会修改传入的参数
var name=["abc","aaa","cccc"]; //* slice是一个纯函数 只要给它传入一个start/end,那么对于同一个数组来说,它会给我们返回确定的值 //* slice本身不会修改原来数组的值 不会产生副作用 var newName=name.slice(0,2); console.log(newName); console.log(name); //* splice不是纯函数 会修改原来数组的组 var newName2=name.splice(1);//返回一个新的数组 console.log(newName2); // [ 'aaa', 'cccc' ] console.log(name); // [ 'abc' ]
// * 是一个纯函数,确定的输入,产生确定的输出
// * 函数没有产生副作用
function foo(num1,num2){
return num1+num2*num2;
}
// * 不是纯函数
//* 产生了副作用,修改了外层的变量的值
var name="abc";
function bar(){
console.log("bar其他的代码执行");
name="cba";
}
bar()
//* 不是纯函数
//* 修改了参数的值,产生了副作用
function baz(info){
info.age=100;
}
var obj={name:"wjy",age:15};
baz(obj);
console.log(obj);
//* 纯函数:相同的输入(值相同),产生相同的输出
function test(info){
return{
...info,
age:100
}
}
1.3.1 争议的题
function printInfo(info){
console.log(info.name,info.age);
}
- 严格模式下,printInfo不是纯函数,因为有输出
- 但非严格模式下,我们觉得只是简单的输出,并不会造成程序的bug,所以还是认为是纯函数
1.3.2 总结
- 纯函数不能修改传入的值、全局的值
- 不能随便的输出东西
1.4 纯函数的优势
- 为什么纯函数在函数式编程非常重要呢?
- 因为你可以安心的编写和安心的使用
- 你在写的时候保证了函数的纯度,只是单纯实现了自己的业务逻辑即可,不需要关心传入的内容是如何获得的或者依赖其他的外部变量是否发生了修改。
- 你在用的时候,你确定你的输入内容不会被任意修改,并且自己确定的输入,一定有确定的输出。
- React中就要求我们无论是函数还是class声明一组件,这个组件必须像纯函数一样,保护它们的props
React非常灵活,但它也有一个严格的规则
所有React组件都必须像一个纯函数一样保护它们的props不被修改
纯函数修改一个对象,会在函数内部返回一个新的对象,在外部用一个对象进行接收
2.JavaScript柯里化(过程不是指函数)
柯里化也是属于 函数式编程 里面一个非常重要的概念。
2.1 维基百科的解释
我们先来看一下 维基百科 的解释 :
- 在计算机科学中,柯里化 (英语:Currying),又译为 卡瑞化 或 加里化
- 是把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术
- 柯里化声称 “如果你固定某些参数,你将得到接受余下参数的一个函数”
总结
- 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数
- 这个过程就称之为柯里化
function foo(m,n,x,y){
return m+n+x+y;
}
//柯里化的过程
function bar(m){
return function(n){
return function(x){
return function(y){
return m+n+x+y;
}
}
}
}
console.log(bar(10)(20)(30)(40));
2.2 柯里化结构
//未柯里化的函数
function add1(x,y,z){
return x+y+z
}
console.log(add1(10,20,30));
// 柯里化处理的函数
function add2(x){
return function(y){
return function (z){
return x+y+z;
}
}
}
console.log(add2(10)(20)(30));
//简化柯里化的代码
var add3=x=>y=>z=>{
return x+y+z;
}
console.log(add3(10)(20)(30));
//未简化的箭头函数,只有执行体只有一句代码时,可以省略外面的大括号和return
var add4=x=>{
return y=>{
return x=>{
return x+y+z
}
}
}
console.log(add4(10)(20)(30));
2.3 柯里化的作用
2.3.1 让函数的职责单一
那么为什么需要柯里化呢?
- 在函数式编程中,我们其实往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理
- 那么我们是否就可以将每次传入的参数在单一的函数中进行处理,处理完后在下一个函数中再使用处理后的结果
- 比如上面的案例我们进行一个修改:传入的函数需要分别被进行如下处理
- 第一个参数 +2
- 第二个参数 *2
- 第三个参数 **2
function add(x,y,z){
x=x+2;
y=y*2;
z=z**2;
return x+y+z;
}
console.log(add(10,20,30));
function sum(x){
x=x+2;
return function(y){
y=y*2;
return function(z){
z=z**2;
return x+y+z;
}
}
}
console.log(sum(10)(20)(30));
2.3.2 逻辑的复用
2.3.2.1 案例1
function sum(m,n){
return m+n;
}
//假如在程序中,外面经常需要把5和另外一个数字进行相加
console.log(sum(5,10));
console.log(sum(5,6));
console.log(sum(5,100));
console.log(sum(5,555));
function makeAdder(count){
return function(num){
return num+count;
}
}
let add5=makeAdder(5);
console.log(add5(10));
console.log(add5(6));
console.log(add5(100));
console.log(add5(555));
2.3.2.2 案例2
// 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")
// 柯里化优化
var log=date=>type=>message=>{
console.log(`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`);
}
//如果我现在打印的都是当前时间
var nowLog=log(new Date())
nowLog("DEBUG")("查找到轮播图的bug")
nowLog("FEATURE")("新增了添加用户的功能")
//如果打印的都是相同的时间和类型
var nowLogAndDebugLog=log(new Date())("DEBUG")
nowLogAndDebugLog("查找到轮播图的bug")
nowLogAndDebugLog("查询菜单的bug")
nowLogAndDebugLog("查询数据的bug")
var nowLogAndFeatureLog=log(new Date())("FEATURE")
nowLogAndFeatureLog("新增了添加用户的功能")
2.4 自动柯里化函数的实现
function add1(x,y,z){
return x+y+z;
}
function add2(x,y,z){
x=x+2;
y=y*2;
z=z**2;
return x+y+z;
}
function makeAdder(count){
return function(num){
return count+num;
}
}
function log(date,type,message){
console.log(`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`);
}
//柯里化函数的实现hyCurrying
/**
*
* @param {*} fn
* * 传入一个函数,返回一个新的函数
*/
function hyCurrying(fn){
function curried(...args){
// 判断当前已经接收的参数的个数,可以判断本身需要接受的参数是否一致了
if(args.length>=fn.length){
//接收的参数已经够了,需要去调用用来的参数
// fn(...args) //这样写的话没有考虑后面绑定的this
return fn.apply(this,args)
}else{
// 如果个数没有达到,则返回一个新的函数
function curried2(...args2){
// 接收到参数后,需要递归调用curried来检查函数的个数是否达到
return curried.apply(this,[...args,...args2])
}
return curried2;
}
}
return curried;
}
var curryAdd=hyCurrying(add1);
console.log(curryAdd(50)(20,30));
// function foo(a,b,c,d,e,f,g){
// }
// // 函数名.length获取参数的个数
// console.log(foo.length);
Vue3为什么使用柯里化,是为了可扩展性,
3.组合函数
组合(Compose)函数是在JavaScript开发过程中一种对函数的使用技巧、模式:
- 比如我们现在需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的;
- 那么如果每次我们都需要进行两个函数的调用****,操作上就会显得重复
- 那么是否可以将这个两个函数组合起来,自动依次调用呢?
- 这个过程就是对函数的组合,我们称之为 组合函数 (Compose Function)
3.1 组合之前
function double(num){
return num*2;
}
function square(num){
return num**2;
}
var count=10;
var result=square(double(count));
console.log(result);
3.2 组合之后
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 m(n(count))
}
}
var newFn=composeFn(square,double);
console.log(newFn(10));
console.log(newFn(20));
console.log(newFn(30));
3.3 通用组合函数的实现
function hyCompose(...fns){
// * 需要考虑一些边界情况,例如传的有可能不是一个函数
var length=fns.length;
for(let i=0;i<length;i++){
if(typeof fns[i]!=='function'){
throw new TypeError("Expected arguments are functions")
}
}
function compose(...args){
let index=0;
let result=length>0?fns[index].apply(this,args);
while(++index<length){
result=fns[index].call(this,result);
}
return result;
}
return compose;
}
function double(m){
return m*2;
}
function square(n){
return n**2
}
var newFn=hyCompose(square,double);
console.log(newFn(10));