当前位置:网站首页 > 前端开发 > 正文

前端function_前端开发者

通用的函数式编程语言,是Haskell,被函数式原教旨主义者认为是纯函数式语言。函数式编程的思想也不断影响着传统编程语言,比如Java 8开始支持lambda表达式,而函数式编程的大厦最初就是基于lambda计算构建起来的。函数式编程对于前端来说是必选项,后端则不必。

React框架的组件从很早开始就是不仅支持类式组件,也支持函数式的组件。而后React Hooks的出现,使得函数式编程思想越来越变得不可或缺。

无副作用

这是函数式编程的精髓所在。

无副作用的函数应该符合下面的特点:

  1. 要有输入参数。如果没有输入参数,这个函数拿不到任意外部信息,也就不用运行了。
  2. 要有返回值。如果有输入没有返回值,又没有副作用,那么这个函数白调了。
  3. 对于确定的输入,有确定的输出

数学函数就是如此的:

const sqr3 = function(x){ return x * x * x; } console.log(sqr3(2));

无副作用函数拥有三个巨大的好处:

  1. 可以进行缓存。我们就可以采用动态规划的方法保存中间值,用来代替实际函数的执行结果,大大提升效率。
  2. 可以进行高并发。因为不依赖于环境,可以调度到另一个线程、worker甚至其它机器上,反正也没有环境依赖。
  3. 容易测试,容易证明正确性。不容易产生偶现问题,也跟环境无关,非常利于测试。

组合函数

在会无副作用的函数之后,需要好的将这些函数组合起来。

上述的 sqr3 函数有个问题,如果不是number类型,计算就会出错。按照命令式的思路,我们可能就直接去修改sqr2的代码,比如改成这样:

/ 命令式 */ const sqr3 = function(x){ if (typeof x === 'number'){ return x * x * x; } return 0; } / 函数式 */ const isNum = x => typeof x === 'number'; console.log(sqr3(isNum("20")));

或者是我们在设计sqr3的时候就先预留出来一个预处理函数的位置,将来要升级就换这个预处理函数,主体逻辑不变:

/ fn 作为 预处理函数 */ const sqr3 = function(fn, x){ const y = fn(x); return y * y; } const sqr3New = function(x){ return sqr3(isNum,x); } console.log((sqr3New(2.2)));

​​​​容器封装函数能力

如果我们想给其他的函数也复用这个isNum的能力,可以封装一个容器对象来提供这个能力:

 class MayBeNumber{ constructor(x){ this.x = x; } map(fn){ if (isNum(this.x)){ return MayBeNumber.of(fn(this.x)); } return MayBeNumber.of(0); } getValue(){ return this.x; } }

这样,我们不管拿到一个什么对象,用其构造一个MayBeNumber对象出来,再调用这个对象的map方法去调用数学函数,就自带了isNum的能力:

const notnum = new MayBeNumber(undefined).map(Math.sin).getValue(); console.log(notnum);

可以发现,输出值从NaN变成了0。而且封装到对象中的另一个好处是可以用"."多次调用了。

再者,使用对象封装之后的另一个好处是,函数嵌套调用跟命令式是相反的顺序,而用map则与命令式一致,先执行的先写:

const num = new MayBeNumber(1).map(Math.sin).map(sqr2).getValue(); console.log(num);

of 封装 new

上面的封装到对象中,但是函数式编程还搞出来new对象再map,最好是构造对象也是个函数。给它定义个 of 方法:

MayBeNumber.of = function(x){ return new MayBeNumber(x); } const num = MayBeNumber.of(2).map(Math.tan).map(Math.exp).getValue(); console.log(num);

再来看下另一种情况,我们处理返回值的时候,如果有Error,就不处理Ok的返回值,可以这么写:

class Result{ constructor(Ok, Err){ this.Ok = Ok; this.Err = Err; } isOk(){ return this.Err === null || this.Err === undefined; } map(fn){ return this.isOk() ? Result.of(fn(this.Ok),this.Err) : Result.of(this.Ok, fn(this.Err)); } } Result.of = function(Ok, Err){ return new Result(Ok, Err); } console.log(Result.of(2, undefined).map(sqr3)); // 输出结果为:Result { Ok: 8, Err: undefined }

这是一种容器的设计模式:

  1. 有一个用于存储值的容器
  2. 这个容器提供一个map函数,作用是map函数使其调用的函数可以跟容器中的值进行计算,最终返回的还是容器的对象

我们可以把这个设计模式叫做Functor函子。如果这个容器还提供一个of函数将值转换成容器,那么它叫做Pointed Functor。比如 JavaScript 中的Array类型。

简化对象层级

借助Result结构,对sqr3的返回值进行格式化。如果是数值的话,Ok是数值,Err是undefined。如果非数值的话,Ok是undefined,Err是0:

const sqr3Res = function(x){ if (isNum(x)){ return Result.of(x * x * x, undefined); } return Result.of(undefined, 0); } console.log(Result.of(4.3, undefined).map(sqr3Res)); // 输出结果:Result { Ok: Result { Ok: 18.49, Err: undefined }, Err: undefined }

返回的是一个嵌套的结果,但是我们需要的是子Result的值。需要个Result 加一个 join函数:

class Result{ constructor(Ok, Err){ this.Ok = Ok; this.Err = Err; } isOk(){ return this.Err === null || this.Err === undefined; } map(fn){ return this.isOk() ? Result.of(fn(this.Ok),this.Err) : Result.of(this.Ok, fn(this.Err)); } join(){ if (this.isOk()) { return this.Ok; } return this.Err; } flatMap(fn){ return this.map(fn).join(); } } Result.of = function(Ok, Err){ return new Result(Ok, Err); } console.log(Result.of(3, undefined).flatMap(sqr3Res)); // 输出结果:Result { Ok: 27, Err: undefined }

不严格地讲,像Result这种实现了flatMap功能的 Pointed Functor,就是传说中的Monad。

偏函数和高阶函数

函数式编程与命令行编程体感上的最大区别:

  1. 函数是一等公式,我们应该熟悉变量中保存函数再对其进行调用
  2. 函数可以出现在返回值里,最重要的用法就是把输入是n(n>2)个参数的函数转换成n个1个参数的串联调用,这就是传说中的柯里化。这种减少了参数的新函数,我们称之为偏函数
  3. 函数可以用做函数的参数,这样的函数称为高阶函数。

如何用函数式方法实现一个只执行一次有效的函数?

once是一个高阶函数,返回值是一个函数,如果done是false,则将done设为true,然后执行fn。done是在返回函数的同一层,所以会被闭包记忆获取到:

const once = (fn) => { let done = false; return function() { return done ? undefined : ((done=true), fn.apply(this,arguments)); } } const initData = once( () => { console.log("Initialize data"); } ); initData(); initData();

可以发现,第二次调用init_data()没有发生任何事情。

递归与记忆

递归是函数式编程中比较复杂的,最简单的递归就是阶乘:

const factorial = (n) => { if (n === 0){ return 1; } return n * factorial(n - 1); } console.log(factorial(10));

如此会重复计算好多次,效率较低,应该使用动态规划或者缓存记忆。没错,我们可以封装一个叫memo的高阶函数来实现这个功能:

const memo = (fn) => { const cache = {}; return (arg) => cache[arg] || (cache[arg] = fn(arg)); }

 使用memo的后阶乘:

 const fastFact = memo( (n) => { if (n <= 0){ return 1; } return n * fastFact(n-1); } );

言归前端,React Hooks 中的 useMemo就是使用的这种记忆机制:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

总结

在日常使用中,需要记住这几点:

  1. 函数式编程的核心就是将函数存到变量里,用在参数里,用在返回值里;
  2. 在编程时要时刻记住将无副作用与有副作用代码分开;
  3. 函数式编程背后有其数学基础,不仅仅是一种设计模式。
到此这篇前端function_前端开发者的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • TypeScript要被淘汰_前端开发用什么软件2024-11-21 08:00:09
  • TypeScript在前端开发中的应用:全面解析与实例2024-11-21 08:00:09
  • typescript写前端_软件是如何开发的2024-11-21 08:00:09
  • typescript前景如何_前端开发常用软件2024-11-21 08:00:09
  • typescript写前端_怎么开发一个软件2024-11-21 08:00:09
  • 前端开发、后端开发、全栈开发的区别_前端开发,后端开发,全栈开发的区别是什么2024-11-21 08:00:09
  • 前端和全栈哪个好找工作_全栈先学前端还是后端2024-11-21 08:00:09
  • 在3个月内完成前端开发(完整路线图)_三个月学前端能找到工作吗2024-11-21 08:00:09
  • 前端全栈开发什么意思_全栈先学前端还是后端2024-11-21 08:00:09
  • 三个月学前端能找到工作吗_前端设计师主要做什么2024-11-21 08:00:09
  • 全屏图片