JS轻量级函数式编程实现XDM二


Posted in Javascript onJune 16, 2022

前言

承接上一篇《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的资料请关注三水点靠木其它相关文章!


Tags in this post...

Javascript 相关文章推荐
Mootools 1.2教程(3) 数组使用简介
Sep 14 Javascript
判断文档离浏览器顶部的距离的方法
Jan 08 Javascript
JS对象与json字符串格式转换实例
Oct 28 Javascript
常用DOM整理
Jun 16 Javascript
jQuery实现textarea自动增长宽高的方法
Dec 18 Javascript
分享一个原生的JavaScript拖动方法
Sep 25 Javascript
Angular.js指令学习中一些重要属性的用法教程
May 24 Javascript
Vue 父子组件数据传递的四种方式( inheritAttrs + $attrs + $listeners)
May 04 Javascript
JS 使用 window对象的print方法实现分页打印功能
May 16 Javascript
bootstrap table表格插件之服务器端分页实例代码
Sep 12 Javascript
angular 实现同步验证器跨字段验证的方法
Apr 11 Javascript
javascript递归函数定义和用法示例分析
Jul 22 Javascript
JS函数式编程实现XDM一
Jun 16 #Javascript
正则表达式基础与常用验证表达式
Jun 16 #Javascript
使用compose函数优化代码提高可读性及扩展性
html中两种获取标签内的值的方法
Jun 16 #jQuery
JavaScript前端面试扁平数据转tree与tree数据扁平化
Jun 14 #Javascript
vue如何在data中引入图片的正确路径
Jun 05 #Vue.js
Vue Mint UI mt-swipe的使用方式
Jun 05 #Vue.js
You might like
PHP simple_html_dom.php+正则 采集文章代码
2009/12/24 PHP
php whois查询API制作方法
2011/06/23 PHP
分享8个最佳的代码片段在线测试网站
2013/06/29 PHP
PHP内置过滤器FILTER使用实例
2014/06/25 PHP
PHP使用trim函数去除字符串左右空格及特殊字符实例
2016/01/07 PHP
Thinkphp5框架实现图片、音频和视频文件的上传功能详解
2019/08/27 PHP
PHP pthreads v3下同步处理synchronized用法示例
2020/02/21 PHP
js getBoundingClientRect() 来获取页面元素的位置
2010/11/25 Javascript
js简单实现根据身份证号码识别性别年龄生日
2013/11/29 Javascript
jQuery - css() 方法示例详解
2014/01/16 Javascript
5个可以帮你理解JavaScript核心闭包和作用域的小例子
2014/10/08 Javascript
分享一个精简的vue.js 图片lazyload插件实例
2017/03/13 Javascript
JS实现键值对遍历json数组功能示例
2018/05/30 Javascript
详解React Native 屏幕适配(炒鸡简单的方法)
2018/06/11 Javascript
Nodejs异步回调之异常处理实例分析
2018/06/22 NodeJs
layui结合form,table的全选、反选v1.0示例讲解
2018/08/15 Javascript
vue 使用async写数字动态加载效果案例
2020/07/18 Javascript
vue2.* element tabs tab-pane 动态加载组件操作
2020/07/19 Javascript
详解vue路由
2020/08/05 Javascript
[31:01]2014 DOTA2国际邀请赛中国区预选赛5.21 CNB VS Orenda
2014/05/23 DOTA
用Python展示动态规则法用以解决重叠子问题的示例
2015/04/02 Python
Python SqlAlchemy动态添加数据表字段实例解析
2018/02/07 Python
攻击者是如何将PHP Phar包伪装成图像以绕过文件类型检测的(推荐)
2018/10/11 Python
对python GUI实现完美进度条的示例详解
2018/12/13 Python
详解用Python为直方图绘制拟合曲线的两种方法
2019/08/21 Python
PHP统计代码行数的小代码
2019/09/19 Python
Python彻底删除文件夹及其子文件方式
2019/12/23 Python
python中Ansible模块的Playbook的具体使用
2020/05/28 Python
利用HTML5 Canvas API绘制矩形的超级攻略
2016/03/21 HTML / CSS
巴基斯坦电子产品购物网站:Home Shopping
2017/09/14 全球购物
饿了么订餐官网:外卖、网上订餐
2019/06/28 全球购物
乌克兰在线商店的价格比较:Price.ua
2019/07/26 全球购物
四风存在的原因分析
2014/02/11 职场文书
乡镇党员干部群众路线对照检查材料思想汇报
2014/09/28 职场文书
2014年汽车销售工作总结
2014/12/01 职场文书
爱心捐款活动总结
2015/05/09 职场文书