【.com原稿】前言函数式编程已经成为前端非常热门的话题。在过去的几年里,我们看到大量的应用程序代码库大量使用了函数式编程思想。本文将省略那些晦涩概念的介绍,重点介绍什么是JavaScript中的函数式代码,声明式代码和命令式代码的区别,以及常见的函数式模型有哪些?一、什么是函数式编程函数式编程是一种编程范式,主要是用函数来封装计算过程,通过各种函数组合来计算结果。函数式编程意味着您可以在更短的时间内编写错误更少的代码。举一个简单的例子,假设我们要将字符串中每个单词的首字母大写functionalprogrammingisgreat,我们可以这样做:varstring='functionalprogrammingisgreat';varresult=string.split('').map(v=>v.slice(0,1).toUpperCase()+v.slice(1)).join('');上面的例子首先使用split将字符串转换为数组,然后将每个元素的首字母映射转换为大写,最后通过join将数组转换为字符串。整个过程就是join(map(split(str))),体现了函数式编程的核心思想:通过函数来??转换数据。由此我们可以看出,函数式编程有两个基本特点:通过函数转换数据,通过多个函数拼接得到结果。:我们通过一条接一条的指令来让计算机执行一些动作,这通常涉及到很多复杂的细节。命令式代码中经常使用语句来完成某种行为。比如for、if、switch、throw等语句。声明式:我们通过写表达式来声明我们想做什么,而不是一步一步的指令。表达式通常是一些函数调用、一些值和计算结果值的运算符的组合。//命令式varCEOs=[];for(vari=0;ic.CEO);从上面的例子我们可以看出,声明式的写法就是一个表达式,不需要关心如何迭代计数器,如何收集返回的数组。它指定做什么,而不是如何做。函数式编程的一个明显好处是这种声明性代码。对于没有副作用的纯函数,我们完全可以忽略函数内部是如何实现的,专注于编写业务代码。3.共同特点很多时候我们在查阅函数式编程的相关资料时,经常会看到如下特点:无副作用是指调用函数时外部状态不会被修改,即函数仍然会返回相同的值调用n次后的结果。vara=1;//包含副作用,它修改了外部变量a//多次调用结果不一样functiontest2(a){returna+1;}透明引用意味着一个函数只会使用传递给它的变量和内部创建的变量,不会使用其他变量。vara=1;varb=2;//函数内部使用的变量不属于其作用域functiontest1(){returna+b;}//函数内部使用的变量显式传入functiontest2(a,b){returna+b;}不可变变量是指变量一旦创建,就不能修改,任何修改都会产生一个新的变量。使用不可变变量的最大好处是线程安全。多个线程可以同时访问同一个不可变变量,更容易实现并行。由于JavaScript本身不支持不可变变量,因此需要通过第三方库来实现。(如Immutable.js,Mori等)varobj=Immutable({a:1});varobj2=obj.set('a',2);console.log(obj);//Immutable({a:1})console.log(obj2);//Immutable({a:2})函数是一等公民我们常说函数是JavaScript的“一等公民”,也就是说函数处于平等地位以其他数据类型为基础,可以赋值给其他变量,也可以作为参数,传递给另一个函数,或者作为另一个函数的返回值。下面要介绍的闭包、高阶函数、函数柯里化、函数组合,都是围绕这个特性的应用。4.常见的函数式编程模型常见的函数式编程模型包括闭包、高阶函数和函数柯里化。下面将详细介绍Lisification和functioncombination:1.闭包(Closure)如果一个函数引用了自由变量,那么这个函数就是一个闭包。什么是自由变量?自由变量是指不属于函数作用域的变量(所有全局变量都是自由变量,严格来说,引用全局变量的函数就是闭包,但是这样的闭包是没有用的,通常我们说闭包就是里面的函数一个函数)。闭包形成条件:有内外两层函数。内部函数引用外部函数的局部变量。闭包的作用:可以定义一些范围有限的持久化变量,这些变量可以用于缓存或者计算中间量等。//简单的缓存工具//匿名函数创建一个闭包constcache=(function(){conststore={};return{get(key){returnstore[key];},set(key,val){store[key]=val;}}}());console.log(cache)//{get:?,set:?}cache.set('a',1);cache.get('a');//1上面的例子是一个简单缓存的实现工具。匿名函数创建一个闭包,这样store对象就可以一直被引用,不会被回收。闭包的缺点:持久化变量不会正常释放,继续占用内存空间,容易造成内存浪费,所以一般需要一些额外的手动清理机制。2.高阶函数函数式编程倾向于重用一组通用的函数来处理数据,这是通过使用高阶函数来实现的。高阶函数是指以函数为参数,或返回函数,或既以函数为参数又返回函数的函数。高阶函数常用于:将行为、动作、异步控制流抽象或隔离为回调函数、promises、monads等,创建可应用于部分数据类型的函数Literizedfunctions,用于多路复用或函数作品。获取一个函数列表并返回一些由该列表中的函数组成的复合函数。JavaScript语言本身就支持高阶函数。例如,Array.prototype.map、Array.prototype.filter和Array.prototype.reduce是JavaScript中内置的高阶函数。使用高阶函数会让我们的代码更加清晰简洁。mapmap()方法创建一个新数组,其结果是对数组中的每个元素调用提供的函数的结果。map不会更改原始数组。假设我们有一个包含name和type属性的对象数组,我们想把这个数组中的所有name属性放到一个新的数组中,如何实现呢?//不要使用高阶函数varanimals=[{name:"Fluffykins",species:"rabbit"},{name:"Caro",species:"dog"},{name:"Hamilton",species:"dog"},{name:"Harold",species:"fish"},{name:"Ursula",species:"cat"},{name:"Jimmy",species:"fish"}];varnames=[];for(leti=0;ix.name);console.log(names);//["Fluffykins","Caro","Hamilton","Harold","Ursula","Jimmy"]filterfilter()方法创建一个新数组,其中包含由回调函数filter为数组中的每个元素调用一次回调函数,回调函数返回true表示该元素通过测试,并保留该元素,false则不保留。filter不会改变原来的数组,它返回新的过滤后的数组。假设我们有一组具有名称和种类属性的对象。我们想创建一个只包含狗(物种:“狗”)的数组。如何现实呢?//不用高阶函数varanimals=[{name:"Fluffykins",species:"rabbit"},{name:"Caro",species:"dog"},{name:"Hamilton",species:"dog"},{name:"Harold",species:"fish"},{name:"Ursula",species:"cat"},{name:"Jimmy",species:"fish"}];vardogs=[];for(vari=0;ix.species==="dog");console.log(dogs);//{name:"Caro",species:"dog"}//{name:"Hamilton",species:"dog"}reduce方法对调用数组的每个元素执行回调函数,最后生成一个值并返回它。reduce方法接受两个参数:1)reducer函数(回调),和2)一个可选的initialValue.假设我们对一个数组求和://不要使用高阶函数constarr=[5,7,1,8,4];letsum=0;for(leti=0;iaccumulator+currentValue,0);console.log(sum)//25我们可以通过下图形象的展示三者的区别:3.函数柯里化柯里化也叫偏求值,柯里化函数它会接收到一些参数,然后不会立即求值,而是会继续返回一个新的函数,以闭包的形式保存传入的参数,等到真正求值时,再执行所有传入的参数立即求值//普通函数functionadd(x,y){returnx+y;}add(1,2);//3//函数柯里化varadd=function(x){returnfunction(y){returnx+y;};};varincrement=add(1);increment(2);//3这里定义了一个add函数,它接受一个参数,返回一个新的函数。调用add之后,返回的函数通过闭包的方式记住add的第一个参数。4.函数组合(Composition)前面提到,函数式编程的特点之一就是通过串联函数来求值。但是,随着级联函数数量的增加,代码的可读性会不断下降。函数组合就是解决这个问题的方法。假设有一个compose函数可以接受多个函数作为参数,然后返回一个新的函数。当我们为这个新函数传递参数时,参数会“流”过其中的函数,最后返回结果。//两个函数的组合varcompose=function(f,g){returnfunction(x){returnf(g(x));};};//或varcompose=(f,g)=>(x=>f(g(x)));varadd1=x=>x+1;varmul5=x=>x*5;compose(mul5,add1)(2);//=>15篇参考文章MastertheJavaScriptInterview:What是函数式编程?SoYouWanttobeaFunctionalProgrammer理解JavaScript中的高阶函数我所了解的函数式编程李兴舟,MOOC.com认证作者,前端爱好者,立志向全栈工程师发展,已聘请从事前端一年多,目前技术栈有vue全家桶,ES6及以下等,乐于分享,去年写了五六篇十篇原创技术文章,获得好评!【原创稿件,合作网站转载请注明原作者和出处为.com】