函数式编程导论(一)
在文章之前,我们先来谈谈对函数式编程的理解,又名。FP)(稍后我会用FP来参考函数式编程):
FP需要保证所有函数都是纯函数,不依赖外部状态变量,不产生副作用。基于这个前提,纯函数的组合和调用不会依赖于时间顺序,改变多个函数的调用顺序时也不用担心出现问题,所以会消除很多潜在的bug。函数必须有输入和输出。如果一个函数缺少输入或输出,它只是一个过程。功能尽可能简单。如果一个函数做很多事情,理论上应该拆分成几个函数。FP的意义之一就是在合适的时候使用声明式编程,抽象出程序流的控制和表达,在理解和维护上比命令式编程更好。FP是一种范式,但并不意味着与OOP(面向对象编程)冲突,两者可以和谐共存。个人觉得React其实是个不错的栗子~ Javascript函数的一等公民和闭包的特性决定了Javascript确实是一个适合FP的理解闭包的阶段
闭包对于Javascript当然非常重要。但是,对于函数式编程来说,这就更有必要了,而必须掌握的概念,闭包的定义如下:
闭包是指一个函数从它自己的作用域之外记住和访问变量,即使该函数是在不同的作用域中执行的。
相信大部分同学对闭包都有很好的理解,但是学习FP很重要。接下来,我再带你看一遍。闭包是一个可以读取其他函数内部变量的函数
简单的例子如下
//闭包demofunction cube(x) {让z=1;返回函数较大(y){ return x * y * z;};} const MakeCube=cube(10);console . log(MakeCube(5));//50 console . log(MakeCube(5));//100你有没有想过在makeCube函数中,或者换句话说,在更大的函数中,如何记住不属于你作用域的变量x和z?在控制台中查看makeCube.prototype,我们发现[[Scopes]]的内置属性中有一个Closure(cube),当函数biger返回时会记住变量x和z。如果你嵌套几层函数,你会在[[作用域]]的作用域[]数组中找到更多的闭包(名称),并依次找到变量。
请看下面的测试代码:
函数立方体(x) {返回函数包装器(y) {让z=1;return函数biger(){ return x * y * z;};} } const MakeCubey=cube(10);const MakeCube=MakeCubey(5);const $ _ _ VAR1 _ _=' 1。这个var只是为了测试。让$__VAR2__='2。这个var只是为了测试。var $__VAR3__='3。这个var只是为了测试。console . log(MakeCubey . prototype,MakeCube . prototype);console . log(MakeCube());//50 console . log(MakeCube());//100 print makeCubeY.prototype:
要打印makeCube.prototype:
通过这些实验,我们可以从另一个角度理解Javascript中的闭包。闭包如何找到不在自己范围内的变量?makeCube函数从[[作用域]]中的闭包(包装器)中找到变量y和z,从闭包(立方体)中找到变量x。Global let和const声明的变量放在Script中,global var声明的变量放在Global中。
在学习FP之前,了解闭包是非常重要的~因为其实很多FP工具函数都是使用闭包的。
工具功能
一元的
const一元=fn=arg=fn(arg);一元函数,当一个函数只传递一个参数时使用。尝试考虑以下情况:
console.log(['1 ',' 2 ',' 3']。map(ParSeint));//[1,NaN,NaN]console.log(['1 ',' 2 ',' 3']。map(一元(ParSeint)));//[1,2,3]parseInt(string,radix)接收两个参数,而map函数中接收的回调函数callback (currentvalue [,index [,array]],第二个参数是index,此时使用parseInt是错误的。当然,除了Array.prototype.map之外,大量内置数组方法中的回调函数都会传递多个参数。如果有只需要第一个参数的适用场景,一元函数会发挥它的价值,不需要修改函数就可以优雅简洁地访问它。(对于一元函数,fn是闭包记忆的变量数据)
身份
常量标识=v=v有些学生看到恒等式会感到困惑。这是干什么用的?我第一眼就被迷惑了?但是考虑以下情况:
console.log([false,1,2,0,' 5 ',true]。过滤器(身份));//[1,2,' 5 ',true]console.log([false,0]。有些(身份));//falseconsole.log([-2,1,' 3']。每一个(身份));//真的怎么样?眼前一亮。没想到身份功能被隐藏了。事实上,虽然identity返回原始值,但在这些函数中,Javascript会将返回值更改为布尔值。例如,过滤器功能。我们可以看到,MDN对filter的定义如下(见带粗线的句子)。
filter()为数组中的每个元素调用一次提供的回调函数,并构造一个所有值的新数组,回调函数为这些值返回一个强制为true的值。
常数
const常量=v=()=v;同样,这个函数.乍一看,我也不知道具体有什么用。但是考虑以下情况:
onst p1=新Promise((解析,拒绝)={ setTimeout(()={解析(' Hello!');}, 200);});p1 .然后(()='Hi ')。然后(console . log);//嗨!p1 .然后(常量(‘Hi’)。然后(console . log);//嗨!p1 .然后(“嗨”)。然后(console . log);//你好!由于Promise.prototype.then只接受函数,如果我只需要传递一个值,那么常量将提供这种便利。当然没有函数式的改进,但是确实提高了可读性,这也是函数式编程的一个优势。
spreadArgs gatherArgs
const spreadArgs=fn=argsArr=fn(.arg sarr);const gatherArgs=fn=(.arg sarr)=fn(arg sarr);嗯,这两个函数的名字是已知的。它们用于扩展函数的所有参数,并收集函数的所有参数。这两个功能明显相反,那么它们的应用场景是什么呢?
spreadArgs函数的示例如下:
函数立方体(x,y,z) {返回x * y * z;}函数make(fn,points){ return fn(points);}console.log(make(cube,[3,4,5]);//nanconsole . log(make(spreadArgs(cube),[3,4,5]);//60 gatherargs函数的示例如下:
函数combineFirstTwo([v1,v2]){ return v1 v2;}console.log([1,2,3,4,5]。reduce(combineferstwo));//未捕获类型错误控制台. log([1,2,3,4,5]。reduce(gatherArgs(combineferstwo));//15看完上面的代码,两个简单的工具函数可以轻松转换一个函数,从而使其适用于另一个场景。如果我们能从现在开始瞥见函数式编程的魅力,那么接下来的两个函数会给你带来更多的惊喜。
部分咖喱
const partial=(fn,presetArgs=(.laterArgs)=fn(.预设目标,laterArgs);const curry=(fn,arity=fn.length,nextCurried)=(nextCurried=prevArgs=nextArg={ const args=[.prevArgs,next arg];if (args.length=arity) { return fn(.args);} else { return NextCurried(args);} })([]);我相信每个人或多或少都应该知道功能核心化。维基百科定义:
在计算机科学中,Currying(英文:Currying),也翻译为Cary或Gary,是一种将接受多个参数的函数转换为接受单个参数(原始函数的第一个参数)的函数,并返回接受剩余参数的新函数并返回结果的技术。
当然,得益于闭包强大的力量,柯立芝的武器诞生于Javascript世界。请先仔细阅读上面关于partiel和curry函数的代码。
来杯咖啡吧~
如下模拟一个ajax函数:
函数ajax(url,params,callback){ setTimeout(()={ callback(` get $ { URL } \ nparams 3360 $ { params } \ ndata : Hello!$ { params } `);});}考虑部分使用场景如下:
const fetchPerson=partial(ajax,' http://some . API/person ');fetchPerson('泰迪熊',console . log);/*获取http://some.api/person参数:泰迪熊数据:你好!泰迪熊*/考虑咖喱的使用场景如下:
const FetchPerson=curry(Ajax)(' http://some . API/person ');const fetchUncleBarney=fetchPerson('巴尼叔叔');fetchUncleBarney(console . log);/*获取http://some.api/person参数:巴尼叔叔数据:你好!巴尼叔叔*/partial和curry的功能在功能上类似,但应用场景不同,但curry比partial更自动化一点。但是!相信看过例题的同学都会有一系列的问号吧?为什么一次传入参数这么麻烦,而不是单独传入多次?原因如下:
最重要的原因是分部函数和curry函数都允许我们通过参数控制在时间和空间上分离函数的调用。传统的函数只能在一次采集完所有参数后才能调用,但有时我们可以提前预设一些参数,在函数最终需要触发时传入剩余参数。偏食和咖喱在这个时候会变得非常有用。partial和curry的存在使得函数的组合更加方便。(功能的组合也计划稍后和大家分享,这里就不赘述了。).当然,最重要的是提高可读性!一开始我可能不这么认为,但如果你练习一下操作经验,可能会有所改变。注:关于函数式编程的实践,您可以使用lodash/fp模块开始。
一些想法因为我也是函数式编程的初学者,如有错误请指正~
接下来我会继续整理FP学习资料,学习实践,并连载一些我对函数式编程的学习和思考,希望能和大家一起进步~
谢谢( ` ) ~
以上是边肖介绍的Javascript函数式编程的详细讲解和集成,希望对大家有所帮助。如果你有任何问题,请给我留言,边肖会及时回复你。非常感谢您对我们网站的支持!
版权声明:函数式编程导论(一)是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。