什么是函数式编程是一种编程范式,将计算机操作视为数学函数计算,避免使用程序状态和可变对象。函数式编程更强调程序执行的结果,而不是执行过程。它提倡用几个简单的执行单元,使计算结果递进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。函数式编程的思维过程完全不同。它侧重于功能而不是过程。它强调的是如何通过函数的组合和变换来解决问题,而不是我写什么样的语句来解决问题。为什么叫函数式编程是基于学术上对函数的定义。函数是对集合与集合之间的转换关系的描述。通过函数的输入将返回一个且仅一个输出值。一个函数其实就是一个关系,或者说是一个映射,这个映射是可以组合的。在我们的编程世界里,我们需要处理的其实只是“数据”和“关系”,而关系就是函数。我们所谓的编程工作,无非就是找一个映射关系。一旦找到关系,问题就解决了。剩下的就是让数据流过这个关系,然后转换成另一个数据。函数式编程的特点是函数是一等公民。您可以像对待任何其他数据类型一样对待它们-将它们存储在数组中,将它们作为参数传递,将它们分配给变量......等等。使用表达式而不是总是返回值的语句//函数式编程-函数作为返回参数constadd=(x)=>{returnplus=(y)=>{returnx+y;}};letplus1=add(1);letplus2=add(2);console.log(plus1(1));//2console.log(plus2(1));//3声明式编程不再告诉计算机如何工作,而是指向我们明确想要达到的结果。与命令式不同,声明式意味着我们编写表达式,而不是逐步说明。以SQL为例,它没有“先做这个,再做那个”的命令,而是一个表达式,指定我们要从数据库中取什么数据。至于如何取数据,由它自己决定。以后无论是升级数据库,还是优化SQL引擎,都完全不需要改变查询语句。无状态和不可变数据(StatelessnessandImmutabledata)这是函数式编程的核心概念:修改而不是修改现有对象。Stateless:主要强调对于一个函数,不管你什么时候运行,都应该和第一次运行时一样,给定相同的输入,给定相同的输出,完全独立于外界的状态变化。//比较Array中的slice和splicelettest=[1,2,3,4,5];//slice是一个纯函数,返回一个新数组console.log(test.slice(0,3));//[1,2,3]console.log(test);//[1,2,3,4,5]//splice会修改参数数组console.log(test.splice(0,3));//[1,2,3]console.log(test);//[4,5]函数应该是纯天然的,没有副作用。纯函数是总是以相同的输入获得相同输出的函数。并且没有任何可观察到的副作用。副作用是指一个函数的内部函数和外部函数之间的相互作用,产生操作以外的结果。比如在函数调用过程中,如果使用并修改了一个外部变量,那么它就是一个有副作用的函数。副作用可能包括但不限于:更改文件系统将记录插入数据库发送http请求可变数据print/log获取用户输入DOM查询访问系统状态纯函数的优点:可缓存性。纯函数可以根据输入进行缓存。可移植性/自文档。可移植性可能意味着序列化函数并通过套接字发送它们。这也意味着代码可以在webworkers中运行。一个纯函数是完全自给自足的,它所需要的一切都是现成的。纯函数的依赖关系清晰,因此更容易观察和理解。可测试(Testable)纯函数使测试更容易。我们不需要伪造一个“真正的”支付网关,也不需要在每次测试后配置和断言状态。只需给函数一个输入并断言输出即可。合理(Reasonable)许多人认为使用纯函数的最大好处是_referentialtransparency_(引用透明)。如果一段代码可以被其执行结果替换而不改变整个程序的行为,则称该段代码是引用透明的。由于纯函数在给定相同输入的情况下始终可以返回相同的输出,因此可以保证它们始终返回相同的结果,从而保证了引用透明性。参考视频讲解:进入学习并行代码我们可以并行运行任何纯函数。因为纯函数根本不需要访问共享内存,而且根据定义,纯函数不能因为副作用而进入竞争条件。面向对象语言的问题在于它们总是带着那些隐含的环境。你只需要一根香蕉,但你得到一只拿着香蕉的大猩猩……整个丛林懒惰求值函数只在需要时执行,不会产生无意义的中间变量。该函数是从头到尾编写的,只有在最后才是调用它产生的实际结果。在函数式编程中有两个操作是必不可少的:Currying和Compose。柯里化将一个接受多个参数的函数变成一个接受单个参数(原函数的第一个参数)的函数,只传递一部分参数给函数调用,让其返回一个函数处理剩下的参数。函数式编程+柯里化,将提取出来的函数部分配置成柯里化后,可以作为参数传入,简化操作流程。//list中每个元素先加1,再加5,再减1letlist=[1,2,3,4,5];//正常做法letlist1=list.map((value)=>{返回值+1;});letlist2=list1.map((value)=>{returnvalue+5;});letlist3=list2.map((value)=>{returnvalue-1;});控制台日志(列表3);//[6,7,8,9,10]//curriedconstchangeList=(num)=>{return(data)=>{returndata+num}};letlist1=list.map(changeList(1)).map(changeList(5)).map(changeList(-1));控制台日志(列表1);//[6,7,8,9,10]returned该函数会记住通过闭包传入的第一个参数。反复调用有点麻烦。我们可以使用一个特殊的curry辅助函数(helperfunction)来使此类函数的定义和调用更加容易。.varcurry=require('lodash').curry;varmatch=curry(function(what,str){returnstr.match(what);});varreplace=curry(function(what,replacement,str){returnstr.replace(what,replacement);});varfilter=curry(function(f,ary){returnary.filter(f);});varmap=curry(function(f,ary){returnary.map(f);});上面的代码遵循一个简单但非常重要的模式。即有策略地将要操作的数据(String、Array)放到最后一个参数中。您可以调用curry函数一次,也可以通过一次只传递一个参数来多次调用它。match(/\s+/g,"helloworld");//['']match(/\s+/g)("helloworld");//['']varhasSpaces=match(/\s+/g);//function(x){returnx.match(/\s+/g)}hasSpaces("helloworld");//['']hasSpaces("spaceless");//null这里是能力通过使用一个或两个参数调用它来“预加载”一个函数,并获得一个记住这些参数的新函数。Curry很好用,在hasSpaces、findSpaces和censored中看到,只要给函数传递一些参数,就可以得到一个新的函数。简单地用map包装一个参数是单个元素的函数,将它变成一个参数是数组的函数。vargetChildren=function(x){returnx.childNodes;};varallTheChildren=map(getChildren);只传递部分参数给函数通常称为_partialcall_(partialapplication),可以大大减少样板代码)。当我们谈论_纯函数_时,我们说它们接受输入并返回输出。curry函数所做的正是:每次使用一个参数调用该函数时,它都会返回一个处理剩余参数的新函数。这是一个输入对应一个输出。即使输出是另一个函数,它也是一个纯函数。函数组合函数组合的目的是将多个函数组合成一个函数。constcompose=(f,g)=>{return(x)=>{returnf(g(x));};};在compose的定义中,g将在f之前执行,从而创建一个从右到左的数据流。组合的概念直接来源于数学课本,从右到左执行更能体现数学的含义。所有组合都有一个属性//关联性varassociative=compose(f,compose(g,h))==compose(compose(f,g),h);//true所以如果我们想把字符串改成大写(假设存在head、reverse、toUpperCase函数),你可以这样写:compose(toUpperCase,compose(head,reverse));//或者compose(compose(toUpperCase,头),反向);associativelaw的最大好处之一是任何一组函数都可以拆开并打包到它们自己的组合中。如何将它们组合在一起并没有放之四海而皆准的答案——我们只是按照自己喜欢的方式拼砌乐高积木。pointfreepointfree模式意味着该函数不必提及它将对哪种数据进行操作。一流的函数、柯里化和组合一起帮助实现这种模式。//不是pointfree,因为提到了数据:wordvarsnakeCase=function(word){returnword.toLowerCase().replace(/\s+/ig,'_');};//pointfreevarsnakeCase=compose(replace(/\s+/ig,'_'),toLowerCase);使用curry,我们可以让每个函数先接收数据,然后操作数据,最后将数据传递给下一个函数。另请注意,在pointfree版本中,wordparameter可用于构造函数;在非pointfree版本中,需要word来执行所有操作。pointfree模式可以帮助我们减少不必要的命名,保持代码的简洁和通用。debug如果在调试组合时遇到困难,可以使用下面这个实用但不纯的trace函数来跟踪代码的执行情况。vartrace=curry(function(tag,x){console.log(tag,x);返回x;});优势更好的管理状态。因为它的目的是无状态的,或者说是无状态的。在正常的DOM开发中,由于DOM的视觉呈现依赖于状态的变化,因此不可避免地会产生很多状态,不同的组件之间可能会相互依赖。使用FP编程可以最大限度地减少这些未知数、优化代码并减少错误。更容易重用。极端的FP代码应该是每一行代码都是一个函数,当然我们不需要那么极端。我们尽量用更纯粹的函数实现流程逻辑,固定输入->固定输出,不受其他外部变量的影响,无副作用。这样,在复用代码时,就无需考虑其内部实现和外部影响。更优雅的组合。从广义上讲,网页由各种组件组成。在更小的尺度上,一个函数也可能由多个小函数组成。参考上面第二点,更强的复用性带来更强大的组合。隐藏的好处。减少代码量,提高可维护性。劣势性能:函数式编程往往会过度封装一个方法,导致上下文切换的性能开销。同时,在JS这样的非函数式语言中,函数式的方式势必比直接写语句指令要慢(引擎会对很多指令做专门的优化)。资源占用:JS中为了实现对象状态的不变性,经常会创建新的对象。因此,垃圾回收(GarbageCollection)的压力远超其他编程方式。这在某些情况下会产生非常严重的问题。递归陷阱:在函数式编程中,为了实现迭代,通常会使用递归。为了减少递归的性能开销,我们经常将递归写成尾递归,以便解析器对其进行优化。但是众所周知,JS是不支持尾递归优化的。代码不容易阅读。特别熟悉FP的人可能会发现这段代码一目了然。对于不熟悉的人来说,遇到写的晦涩难懂的代码,要想看懂代码,就得在脑子里盘算半个小时。
