当前位置: 首页 > Web前端 > JavaScript

JS与函数式编程

时间:2023-03-27 12:12:19 JavaScript

React16.8中加入了Hook特性,增强了函数式组件的功能;而Vue3.0引入组合API,让人不禁疑惑为什么主流的前端框架都这么推崇函数式编程?面向对象编程不行了吗?首先,你需要了解函数式编程,一种已经存在很长时间(比OO更久)的编程范式。什么是函数式编程?函数式编程是一种编程范式,主要是用函数来封装计算过程,通过组合各种函数来计算结果。函数式编程的特点1.函数是“一等公民”。所谓“一等公民”,就是函数与其他数据类型处于平等地位,可以赋值给其他变量,也可以作为参数传递给其他变量。一个函数,或者作为另一个函数的返回值。在JavaScript中,函数也是对象,一等公民的地位是应有尽有。比如最常见的setTimeout函数,它的第一个参数是一个函数:setTimeout(()=>{console.log('hello')},1000)2.只用“表达式”而不是“语句”来表达“Formula”(表达式)是一个简单的运算过程,总是有一个返回值;“语句”(statement)是执行某种操作,没有返回值。函数式编程要求只使用表达式,而不是语句。也就是说,每一步都是一个简单的操作,并且都有返回值。3.没有“副作用”所谓“副作用”是指函数内外相互作用,产生除运算以外的其他结果。例如:发送HTTP请求,修改全局变量等。纯函数式编程语言写的函数是没有变量的。因此,只要任何函数的输入是确定的,输出也是确定的。据说这个纯函数没有副作用。并且由于Python、JavaScript等语言允许使用变量,所以它们并不是纯粹的函数式编程语言,只是对函数式编程提供了部分支持。函数式编程强调没有“副作用”,就是说函数要保持独立,返回一个新值,不能有其他行为,尤其是外部变量的值不能被修改。在其他类型的语言中,变量通常用于保存“状态”。变量没有被修改,这意味着状态不能保存在变量中。函数式编程使用参数来保存状态,最好的例子就是递归。由于递归调用会消耗大量内存,尤其是递归深度较深时,容易出现栈溢出。可以通过尾递归进行优化。4.惰性求值惰性求值(也称为按需调用)是一种在将表达式分配给变量(或绑定)时不计算表达式值的技术,该值在变量首次使用时求值。这可以通过避免不必要的评估来提高性能。最常见的例子就是Vue中路由的懒加载:constList=()=>import('@/components/list.vue')//只定义一个函数,不执行import动作constrouter=newVueRouter({routes:[{path:'/list',component:List}]})5.引用透明引用透明是指函数的运行不依赖于外部变量或“状态”,而只依赖于输入参数,任何时候只要参数相同,引用函数得到的返回值总是相同的。在其他类型的语言中,函数的返回值往往与系统状态有关,不同状态下返回值不同。这称为“引用不透明”,很难观察和理解程序的行为。6、无锁并发函数式编程不需要考虑“死锁”(deadlock),因为它不修改变量,所以根本不存在“锁死”线程的问题。你不必担心一个线程的数据被另一个线程修改,所以你可以安全地将工作分配给多个线程并部署“并发”。看下面的代码:lets1=Op1()lets2=Op2()lets3=concat(s1,s2)由于s1和s2互不干扰,不会修改变量,所以谁先执行并不重要,所以大家可以放心添加线程,分配到两个线程上完成。其他类型的语言做不到这一点,因为s1可能会修改系统状态,而s2可能会使用这些状态,所以必须保证s2在s1之后运行,自然不能部署到其他线程。文章开头提到的函数式编程和编程范式:函数式编程是一种编程范式。那么主流的编程范式有哪些呢?共有三种常见的编程范式:命令式编程、面向对象编程和函数式编程。命令式编程命令式编程关注解决问题的步骤,而函数式编程关注数据的映射。例如,现在有这样一个数学表达式:(1+2)*3-4命令式编程可以这样写:leta=1+2;让b=a*3;让c=b-4;function由于公式编程需要用到函数,我们可以将运算过程定义为不同的函数,然后写成如下:letresult=subtract(multiply(add(1,2),3),4)求一个最好的问题描述?抽象(Abstraction)通常是我们用来简化复杂现实世界问题的方法。在面向对象编程中,计算机程序被设计为相互关联的对象。对象是类的实例。它把对象作为程序的基本单位,将程序和数据封装在其中,以提高软件的可重用性、灵活性和可扩展性。对象中的程序可以访问并经常修改与对象关联的数据。对象包含数据(字段、属性)和方法。三种编程范式比较这三种编程范式的特点:命令式编程的核心在于模块化,在实现过程中使用状态,依赖外部变量,容易影响附近代码,可读性差,后期维护成本高也很高;函数式编程的核心是避免副作用,不改变或依赖当前函数之外的数据。结合不可变数据和一等公民函数等特性,函数具有自描述性和高可读性;面向对象编程的核心在于抽象,抽象提供了清晰的对象边界。结合封装、集成、多态,降低代码耦合,提高系统可维护性;JS中的函数式编程如前所述,由于JavaScript允许使用变量,因此它不是一种纯粹的函数式编程语言。但毫无疑问,JS中有很多函数式编程的应用,比如ES5/ES6标准中的箭头函数、迭代器、map、filter、reduce等。在前端框架中,Redux的纯函数,React16.8引入的hooks,Vue3.0的组合API等,也是函数式编程的应用。函数柯里化函数柯里化是指将多元函数转换为顺序调用的单元函数。比如实现一个求和函数:add(1,2,3)//一次接受3个参数,通过函数柯里化处理,变成这样:addCurry(1)(2)(3)//接受一个一次参数通常,我们在实践中使用柯里化来使函数成为单值的,这样可以增加函数的多样性,使其更适用:constreplace=curry((a,b,str)=>str.replace(a,b))//curry是实现柯里化的函数,参数是函数constreplaceSpaceWith=replace(/\s*/)constreplaceSpaceWithComma=replaceSpaceWith(',')constreplaceSpaceWithDash=replaceSpaceWith('-')通过上面的方法,我们可以从一个replace函数中生成很多新的函数,可以用在各种场合。手动实现functioncurrying:functioncurry(fn,...args){returnargs.lengthx+1constg=x=>x*2constt=(x,y)=>x+y如果传入参数为1和2,则处理顺序为:f=>g=>t,结果是3=>6=>7。如果有函数compose可以实现函数组合,那么:letfgt=compose(f,g,t)fgt(1,2)//3->6->7可以手动实现compose函数:constcompose=(...fns)=>(...args)=>fns.reduceRight((val,fn)=>fn.apply(null,[].concat(val)),args)函数组合的另一种应用,例如,要将数组的最后一个元素大写,假设存在log、head、reverse和toUpperCase函数。命令式写法:log(toUpperCase(head(reverse(arr))))面向对象写法:arr.reverse().head().toUpperCase().log()现在通过组合,如何实现前面的功能:constupperLastItem=compose(log,toUpperCase,head,reverse)高阶函数高阶函数通常是指以一个或多个函数为参数,或返回一个函数作为结果的函数。在JavaScript中,最常用的高阶函数是:filter、map和reduce。React中的函数式编程可以从以下特点中发现:函数式组件和HookHook是React16.8的新特性,可以在不使用类组件特性的情况下使用状态和其他React。这使得功能组件更加强大。那么功能组件VS类组件,该如何选择呢?首先,两者在开发的编程范式上存在着巨大的差异。类组件基于面向对象编程,着重于继承、生命周期等核心概念;而函数组件的核心是函数式编程,强调不可变、无副作用、透明引用等特性;由于ReactHooks的引入,生命周期概念逐渐淡出,函数组件可以完全替代类组件。其次,继承并不是组件最好的设计模式。官方更喜欢“组合优于继承”的设计理念,因此类组件在这方面的优势也逐渐淡出;在性能优化方面,类组件主要依赖shouldComponentUpdate阻塞渲染提升性能,而函数组件依赖useMemo和useCallback缓存渲染结果提升性能,useEffect阻塞渲染;从入门的角度来说,类组件更容易上手。从未来趋势来看,由于ReactHooks的引入,功能组件成为未来社区提出的主要解决方案;在未来的时间分片和并发模式下,类组件由于生命周期带来的复杂性,不容易优化。功能组件本身轻量简单,在Hooks的基础上,提供了比以前更细粒度的逻辑组织和复用,更适合React未来的发展;数据是不可变的。在React中,强调组件不能被移除。修改传入的prop值,遵循Immutable原则;在Redux中,强调Immutable的作用,每个reducer不能修改状态,只能返回一个新的状态;React中的纯函数,组件render函数应该是纯函数。只有这样,组件渲染的结果才与状态/道具有关,遵循公式UI=f(state);在Redux中,reducer必须是纯函数,这也是函数式编程的要求;React中的高级组件Orderofcomponents(HOC)也是函数式编程的一种应用。高阶组件是一个函数,它接受一个组件并返回一个新组件。高阶组件就是设计模式中的装饰器模式。HOC是纯函数,没有副作用。constEnhancedComponent=highOrderComponent(WrappedComponent);高阶组件是React中实现代码复用的方式之一。Vue中的函数式编程CompositionAPIVue3中的组合API让人想起React中的Hook。这不也是函数式编程吗?Vue2是一个可选的API,它将mounted、data、computed和watch等方法导出为对象的属性;vue3新增了一个入口函数setup,value、computed、watch、onMounted等方法需要从Externalimport中导入;在vue3中,我们可以把这个组件的JS逻辑部分写成方法一样,按需使用import引入。这样做的好处是显而易见的。首先,我们需要编写更少的代码。第二,我们可以封装更多的子函数,引用更多的公共函数来维护我们的代码。第三,代码的可读性变高了。.参考链接函数式编程4种常见编程范式初探编程范式与函数式编程的比较简明JavaScript函数式编程-React世界的函数式编程入门