当前位置: 首页 > 后端技术 > Node.js

本文带你了解什么是JavaScript函数式编程?

时间:2023-04-03 23:01:27 Node.js

前言函数式编程已经成为前端非常热门的话题。在过去的几年里,我们看到大量的应用程序代码库大量使用了函数式编程思想。本文将省略那些晦涩概念的介绍,重点介绍什么是JavaScript中的函数式代码,声明式代码和命令式代码的区别,以及常见的函数式模型有哪些?想阅读更多优质文章,请戳GitHub博客1.什么是函数式编程函数式编程是一种编程范式,主要是用函数封装计算过程,通过组合各种函数来计算结果。函数式编程意味着您可以在更短的时间内编写错误更少的代码。举个简单的例子,假设我们想把字符串中每个单词的首字母大写functionalprogrammingisgreat,我们可以这样做:varstring='functionalprogrammingisgreat';varresult=string.split('').map(v=>v.slice(0,1).toUpperCase()+v.slice(1)).join('');上面的例子先用split把字符串转成数组,然后maps把每个元素的首字母转成大写,最后通过join把数组转成字符串。整个过程就是join(map(split(str))),体现了函数式编程的核心思想:通过函数来??转换数据。由此我们可以看出,函数式编程有两个基本特征:通过函数转换数据,通过多个函数拼接得到结果;计算机执行操作,通常涉及许多复杂的细节。命令式代码中经常使用语句来完成某种行为。比如for、if、switch、throw等语句。声明式:我们通过写表达式来声明我们想做什么,而不是一步一步的指令。表达式通常是一些函数调用、一些值和计算结果值的运算符的组合。//命令式varCEOs=[];for(vari=0;ic.首席执行官);从上面的例子我们可以看出,声明式的写法就是一个表达式,不需要关心如何迭代计数器,如何收集返回的数组,它指定了做什么,而不是如何去做。函数式编程的一个明显好处是这种声明性代码。对于没有副作用的纯函数,我们完全可以忽略函数内部是如何实现的,专注于编写业务代码。3.共同特点无副作用是指函数调用时外部状态不会被修改,即一个函数在n次后仍会返回相同的结果。vara=1;//包含副作用,它修改了外部变量a//多次调用的结果不一样functiontest1(){a++returna;}//没有副作用,不修改外部状态//多次调用的结果都是一样的functiontest2(a){returna+1;}透明引用是指一个函数只会使用传递给它的变量和内部创建的变量,不会使用其他变量。vara=1;varb=2;//函数内部使用的变量不属于其作用域functiontest1(){returna+b;}//函数内部使用的变量显式传入函数test2(a,b){returna+b;}不可变变量是指变量一旦创建就不能修改,任何修改都会产生一个新的变量。使用不可变变量的最大好处是线程安全。多个线程可以同时访问同一个不可变变量,更容易实现并行。由于JavaScript本身不支持不可变变量,因此需要通过第三方库来实现。(如Immutable.js、Mori等)varobj=Immutable({a:1});varobj2=obj.set('a',2);控制台日志(对象);//不可变的({a:1})console.log(obj2);//Immutable({a:2})函数是一等公民我们常说函数是JavaScript的“一等公民”,也就是说函数和其他数据类型一样,处于Equal状态,可以被赋值到其他变量,也可以用作参数,传递给另一个函数,或用作另一个函数的返回值。下面要介绍的闭包、高阶函数、函数柯里化、函数组合,都是围绕这个特性的应用。四、常见的函数式编程模型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、monad等。创建可跨各种数据类型使用的函数部分应用于函数参数(部分函数应用)或创建一个KeLiterized函数,用于多路复用或函数组合。获取一个函数列表并返回一些由该列表中的函数组成的复合函数。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:"鱼"}];varnames=[];for(leti=0;ix.name);console.log(names);//["Fluffykins","Caro","Hamilton","Harold","Ursula","Jimmy"]filterfilter()方法创建一个包含所有通过回调函数测试的元素的新数组。过滤器为数组中的每个元素调用一次回调函数。回调函数返回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:"鱼"}];vardogs=[];for(vari=0;ix.species==="dog");console.log(dogs);//{name:"Caro",species:"dog"}//{name:"Hamilton",species:"dog"}reducereduce方法对调用数组的每个元素执行回调函数,最终生成单个值并返回它。reduce方法接受两个参数:1)reducer函数(回调),以及2)一个可选的initialValue。假设我们想对一个数组求和://不使用高阶函数constarr=[5,7,1,8,4];letsum=0;for(leti=0;iaccumulator+currentValue,0);console.log(sum)//25我们可以通过下图形象的展示三者的区别:3.函数柯里化也叫partialEvaluation,被柯里化的函数会收到一些参数,然后不会立即求值,而是会继续返回一个新的函数,将传入的参数以闭包的形式保存起来,等到真正被求值的时候,然后一次性对所有传入的参数求值。//普通函数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的第一个参数。那么,我们如何实现一个简单的柯里化函数呢?functioncurryIt(fn){//参数fn的参数个数functionvarn=fn.length;变量参数=[];返回函数(arg){args.push(arg);如果(args.length(x=>f(g(x)));varadd1=x=>x+1;varmul5=x=>x*5;compose(mul5,add1)(2);//=>15为大家推荐一款好用的BUG监控工具Fundebug,欢迎免费试用!欢迎关注公众号:前端工匠,让我们一起见证你的成长!参考文章珠峰架构教程(强烈推荐)MDN文档WhatisFunctionalProgramming?SoYouWanttobeaFunctionalProgrammer理解JavaScript中的高阶函数我所了解的函数式编程JS函数式编程指南JavaScript函数式编程(一)我眼中的JavaScript函数式编程