JS轻量级函数式编程实现XDM二
前言
承接上一篇《XDM,JS如何函数式编程?看这就够了!(一)》,我们知道了函数式编程的几个基本概念。
这里作简要回顾:
- 函数式编程目的是为了数据流更加明显,从而代码更具可读性;
- 函数需要一个或多个输入(理想情况下只需一个!)和一个输出,输入输出是显式的代码将更好阅读;
- 闭包是高阶函数的基础;
- 警惕匿名函数;
- 弃用 this 指向;
本篇将着重介绍第 2 点中函数的输入,它是 JS 轻量函数式编程的基础之基础,重要之重要!!!
偏函数
传参现状
我们经常会写出这样的代码:
function ajax(url,data,callback) { // .. } function getPerson(data,cb) { ajax( "http://some.api/person", data, cb ); }
ajax 函数有三个入参,在 getPerson 函数里调用,其中 url 已确定,data 和 cb 两个参数则等待传入。(因为很多时候参数都不是在当前能确定的,需要等待其它函数的操作后确定了再继续传入)
但是我们的原则是:入参最理想的情况下只需一个!
怎样优化,可以实现这一点呢?
我们或许可以在外层再套一个函数来进一步确定传参,比如:
function getCurrentUser(cb) { ...// 通过某些操作拿到 CURRENT_USER_ID getPerson( { user: CURRENT_USER_ID }, cb ); }
这样,data 参数也已经确定,cb 参数仍等待传入;函数 getCurrentUser 就只有一个入参了!
数据的传递路线是:
ajax(url,data,callback) => getPerson(data,cb) => getCurrentUser(cb)
这样函数参数个数逐渐减少的过程就是偏应用。
也可以说:getCurrentUser(cb) 是 getOrder(data,cb) 的偏函数,getOrder(data,cb) 是 ajax(url,data,cb) 函数的偏函数。
设想下:
如果一个函数是这样的:
function receiveMultiParam(a,b,c,......,x,y,z){ // .. }
我们难道还要像上面那样手动指定外层函数进行逐层嵌套吗?
显示我们不会这么做!
封装 partial
我们只需要封装一个 partial(..) 函数:
function partial(fn,...presetArgs) { return function partiallyApplied(...laterArgs){ return fn( ...presetArgs, ...laterArgs ); }; }
它的基础逻辑是:
var partial = (fn, ...presetArgs) => (...laterArgs) => fn( ...presetArgs, ...laterArgs );
把函数作为入参!还记得我们之前所说:
一个函数如果可以接受或返回一个甚至多个函数,它被叫做高阶函数。
我们借用 partial() 来实现上述举例:
var getPerson = partial( ajax, "http://some.api/person" ); var getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } ); // 版本 1
以下函数内部分析非常重要:
运行机制
getPerson() 的内部运行机制是:
var getPerson = function partiallyApplied(...laterArgs) { return ajax( "http://some.api/person", ...laterArgs ); };
getCurrentUser() 的内部运行机制是:
var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs) { var getPerson = function innerPartiallyApplied(...innerLaterArgs){ return ajax( "http://some.api/person", ...innerLaterArgs ); }; return getPerson( { user: CURRENT_USER_ID }, ...outerLaterArgs ); }
数据进行了传递:
getCurrentUser(outerLaterArgs) => getPerson(innerLaterArgs) => ajax(...params)
我们通过这样一层额外的函数包装层,实现了更加强大的数据传递,
我们将需要减少参数输入的函数传入 partial()中作为第一个参数,剩下的是 presetArgs,当前已知几个,就可以写几个。还有不确定的入参 laterArgs,可以在确定后继续追加。
像这样进行额外的高阶函数包装层,是函数式编程的精髓所在!
“随着本系列的继续深入,我们将会把许多函数互相包装起来。记住,这就是函数式编程!” —— 《JavaScript 轻量级函数式编程》
实际上,实现 getCurrentUser() 还可以这样写:
// 版本 2 var getCurrentUser = partial( ajax, "http://some.api/person", { user: CURRENT_USER_ID } ); // 内部实现机制 var getCurrentUser = function partiallyApplied(...laterArgs) { return ajax( "http://some.api/person", { user: CURRENT_USER_ID }, ...laterArgs ); };
但是版本 1 因为重用了已经定义好的函数,所以它在表达上更清晰一些。它被认为更加贴合函数式编程精神!
拓展 partial
我们再看看 partial() 函数还可它用:
function partial(fn,...presetArgs) { return function partiallyApplied(...laterArgs){ return fn( ...presetArgs, ...laterArgs ); }; }
比如:将数组 [1,2,3,4,5] 每项都加 3,通常我们会这么做:
function add(x,y) { return x + y [1,2,3,4,5].map( function adder(val){ return add( 3, val ); } ); // [4,5,6,7,8]
借助 partial():
[1,2,3,4,5].map( partial( add, 3 ) ); // [4,5,6,7,8]
add(..) 不能直接传入 map(..) 函数里,通过偏应用进行处理后则能传入;
实际上,partial() 函数还可以有很多变体:
回想我们之前调用 Ajax 函数的方式:ajax( url, data, cb )。如果要偏应用 cb 而稍后再指定 data 和 url 参数,我们应该怎么做呢?
function reverseArgs(fn) { return function argsReversed(...args){ return fn( ...args.reverse() ); }; } function partialRight( fn, ...presetArgs ) { return reverseArgs( partial( reverseArgs( fn ), ...presetArgs.reverse() ) ); } var cacheResult = partialRight( ajax, function onResult(obj){ cache[obj.id] = obj; }); // 处理后: cacheResult( "http://some.api/person", { user: CURRENT_USER_ID } );
柯里化
函数柯里化实际上是一种特殊的偏函数。
我们用 curry(..) 函数来实现此前的 ajax(..) 例子,它会是这样的:
var curriedAjax = curry( ajax ); var personFetcher = curriedAjax( "http://some.api/person" ); var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } ); getCurrentUser( function foundUser(user){ /* .. */ } );
柯里化函数:接收单一实参(实参个数:1)并返回另一个接收下一个实参的函数。
它将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。
实现:
function curry(fn,arity = fn.length) { return (function nextCurried(prevArgs){ return function curried(nextArg){ var args = prevArgs.concat( [nextArg] ); if (args.length >= arity) { return fn( ...args ); } else { return nextCurried( args ); } }; })( [] ); }
阶段小结
我们为什么要如此着重去谈“偏函数”(partial(sum,1,2)(3))或“柯里化”(sum(1)(2)(3))呢?
第一,是显而易见的,偏函数或柯里化,可以将“指定分离实参”的时机和地方独立开来;
第二,更有重要意义的是,当函数只有一个形参时,我们能够比较容易地组合它们。这种单元函数,便于进行后续的组合函数;
对函数进行包装,使其成为一个高阶函数是函数式编程的精髓!
至此,有了“偏函数”这门武器大炮,我们将逐渐轰开 JS轻量级函数式编程的面纱 ~
以上就是JS轻量级函数式编程实现XDM二的详细内容,更多关于JS轻量级函数式编程XDM的资料请关注猪先飞其它相关文章!
原文出处:https://juejin.cn/post/6969016132741103624
相关文章
- 本篇文章主要分享了通过window.navigator来判断浏览器及其版本信息的实例代码。具有一定的参考价值,下面跟着小编一起来看下吧...2017-01-23
- 这篇文章主要介绍了js如何实现浏览器打印功能,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-15
- 下面小编就为大家带来一篇利用JS实现点击按钮后图片自动切换的简单方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-25
- 这篇文章主要给大家介绍了关于Nest.js参数校验和自定义返回数据格式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-28
- 作为前端,一直以来都知道HTTP劫持与XSS跨站脚本、CSRF跨站请求伪造。防御这些劫持最好的方法是从后端入手,前端能做的太少。而且由于源码的暴露,攻击者很容易绕过防御手段。但这不代表我们去了解这块的相关知识是没意义的,本文的许多方法,用在其他方面也是大有作用。...2021-05-24
- 那么今天我就用JavaScript代码来实现这个效果吧,那么首先介绍一下整个的思路,首先我们先将确定输入密码的位数,我的需求是5位,那么就用一个div标签包住5个input标签...2016-01-02
- 这篇文章主要为大家详细介绍了js+css实现回到顶部按钮back to top回到顶部按钮,感兴趣的小伙伴们可以参考一下...2016-03-03
- 这篇文章主要为大家详细介绍了js实现上传图片及时预览的相关资料,具有一定的参考价值,感兴趣的朋友可以参考一下...2016-05-09
- 这篇文章主要介绍了js实现调用网络摄像头及常见错误处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-07
- 这篇文章主要为大家详细介绍了JS实现随机生成验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-06
- 这篇文章主要给大家介绍了一个关于JS正则匹配的踩坑记录,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-13
- 这篇文章主要介绍了js组件SlotMachine实现图片切换效果制作抽奖系统的相关资料,需要的朋友可以参考下...2016-04-19
- Vue.js通过简洁的API提供高效的数据绑定和灵活的组件系统.这篇文章主要介绍了vue.js 表格分页ajax 异步加载数据的相关资料,需要的朋友可以参考下...2016-10-20
- 为了网站的安全性,很多朋友都把密码设的比较复杂,但是如何密码不能明显示,不知道输的是对是错,为了安全起见可以把密码显示的,那么基于js代码如何实现的呢?下面通过本文给大家介绍JavaScript实现表单密码的隐藏和显示,需要的朋友参考下...2016-03-03
- 这篇文章主要为大家详细介绍了js实现列表按字母排序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-11
- 这篇文章主要介绍了如何使用JavaScript实现“无缝滚动 自动播放”轮播图效果,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-08-20
- 这篇文章主要介绍了基于JavaScript实现文字超出部分隐藏 的相关资料,需要的朋友可以参考下...2016-03-01
- 这篇文章主要介绍了JS实现响应鼠标点击动画渐变弹出层效果代码,具有非常自然流畅的动画过度效果,涉及JavaScript针对鼠标事件的响应及页面元素样式的动态操作相关技巧,需要的朋友可以参考下...2016-03-28
- 这次文章要给大家介绍的是node.JS md5加密中文与php结果不一致怎么办,不知道具体解决办法的下面跟小编一起来看看。 因项目需要,需要Node.js与PHP做接口调用,发现nod...2017-07-06
- 系统的学习了一下angularjs,发现angularjs的有些思想根php的模块smarty很像,例如数据绑定,filter。如果对smarty比较熟悉的话,学习angularjs会比较容易一点,这篇文章给大家介绍angularjs filter用法详解,感兴趣的朋友一起学习吧...2015-12-29