JS纯函数 柯里化函数 组合函数

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));

4.总结

纯函数、柯里化函数、组合函数.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容