资讯专栏INFORMATION COLUMN

函数式 js 接口实现原理,以及 lodash/fp 模块

asce1885 / 804人阅读

摘要:函数式接口之前在上看到一个技术视频,讲的接口为什么不好用,以及什么样的接口更好用。演讲者是的作者,他提出了一种全面函数式的接口设计模式。言归正传,今天聊聊这样的接口如何实现,以及中的模块。

函数式 js 接口

之前在 youtube 上看到一个技术视频,讲“underscore.js的接口为什么不好用”,以及什么样的接口更好用。演讲者是 lodash.js 的作者,他提出了一种“全面函数式”的 js 接口设计模式。大概类似这样:

// 传统接口
_.map([1, 2, 3], function (el) {return el * 2}); // return [2, 4, 6]

// 函数式接口
var fn = _.map([1, 2, 3]); // return a function
fn(function (el) {return el * 2}); // return [2, 4, 6];

// 或者
_.map([1, 2, 3])(function (el) {return el * 2}); // return [2, 4, 6];

找到一点感觉没有?其实就是函数式编程语言中广泛存在的“科里化”函数。当实参填满形参表的时候,执行结算返回结果,否则返回一个临时函数,继续接受实参。

看到这个写法眼前一亮,感觉有大规模简化代码的潜力。当时实际试了一下发下很多地方用不了,因为之前写的代码受 jQuery 影响,有很多这样的接口:

foobar.attribute(name); // 读属性
foobar.attribute(name, newValue); // 写属性

这样的接口是按照上述方法 curry 化会使得读属性变得不可能,根本原因是参数数量不同时 attribute 函数的语义根本不一样。使用 jQuery 的时候感觉这种写法非常爽,后来就跟着这么写,但是目前看来这样的接口设计是有问题的。

言归正传,今天聊聊这样的接口如何实现,以及 lodash 中的 fp 模块。

实现原理

说到底就是个 currying 的问题,currying 在很多语言中是内置功能,但是 js 没有,所以我们要实现一个 currying 工具函数。首先贴一个最简易的 currying 实现,它的功能非常简单,输入一个函数 fn1 和部分实参,返回一个保存部分实参,继续接收实参的函数 fn2,调用fn2,它会合并实参数组,并调用 fn1。

/**
 * 函数柯里化
 * @param fn 输入函数
 * @return 柯里化后的函数
 */
var curry = function (fn) {
    if (!isFunction(fn)) {
        return;
    }

    var args = slice(arguments, 1);
 
    return function () {
        return fn.apply(this, args.concat(slice(arguments, 0)));
    }
}

isFunction 和 slice 大家都知道我就不贴了。看一下如何调用:

function add(a, b) {
    return a + b;
}

addOne = curry(add, 1);

addOne(2); // return 3

有时候我们需要输入的部分实参是数组列表形式,所以我们包装一下刚才的 curry 函数:

/**
 * 函数柯里化
 * @param fn 输入函数
 * @param arr 参数列表
 * @return 柯里化后的函数
 */
var curryApply = function (fn, arr) {
    if (!isFunction(fn)) {
        return;
    }

    var args = arr.slice(0);
    args.unshift(fn);
    return curry.apply(this, args);
}

上面的 curry 函数有个问题,就是连续多次补充实参,我们还需要封装一个支持连续调用的版本:

/**
 * 自动柯里化
 * @param fn 输入函数
 * @param n 输入函数参数个数
 * @return 柯里化后的函数
 */
var autoCurry = function (fn, n) {
    if (!isFunction(fn)) {
        return;
    }

    function retFn() {
        var len = arguments.length;
        var args = slice(arguments, 0);
        var nextn = n - len;
 
        if (nextn > 0) {
            return autoCurry(curryApply(retFn, args), nextn);
        }
    
        return fn.apply(this, args);
    }
    
    return retFn;
}

autoCurry 使用的递归的方法,输出函数可以可以通过简单调用的方式连续补充实参,当实参和预设的参数数量相等时,执行输入函数。使用方法如下:

function compute(a, b, c) {
    return (a + b) * c;
}

var curryedCompute = autoCurry(compute, 3);

compute(1, 2, 3); // return 9
curryedCompute(1)(2)(3); // return 9

大家如果使用 node.js 的话,可能知道 npm 中有个 curry 模块,实现的功能是一样的,不同的是当你不输入参数个数 n 时,curry 模块 会使用 Function 对象的 length 属性作为预设的 n 值。

lodash/fp

到这里实现原理就讲清楚了。本着不造轮子的原则,如果大家想尝试一下函数式风格的基础 js 库的话,建议使用 lodash/fp 这个模块。大家都知道 lodash 是 underscore 的 better implemention,而 lodash/fp 就是科里化的 lodash。与简单的 currying 不同的是,为了方便使用,lodash/fp 的设计者调换了一些接口的参数顺序,比如开头提到的 _.map 接口,如果简单 currying 的话第一个参数应该是数组[1, 2, 3],但是大多数时候,我们想要持有的是一个算法,用这个算法处理不同的数据。所以我们希望暂存的实际上是第二个参数 fn,所以 lodash/fp 的接口是这样的:

// The `lodash/map` iteratee receives three arguments:
// (value, index|key, collection)
_.map(["6", "8", "10"], parseInt);
// → [6, NaN, 2]

// The `lodash/fp/map` iteratee is capped at one argument:
// (value)
fp.map(parseInt)(["6", "8", "10"]);
// → [6, 8, 10]

关于 lodash/fp 更详细的说明,请看:https://github.com/lodash/lodash/wiki/FP-Guide

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79724.html

相关文章

  • 翻译连载 | 附录 C:函数编程函数库-《JavaScript轻量级函数编程》 |《你不知道的J

    摘要:为了尽可能提升互通性,已经成为函数式编程库遵循的实际标准。与轻量级函数式编程的概念相反,它以火力全开的姿态进军的函数式编程世界。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTML 最坚实的梁柱;分享,是 CSS 里最闪耀的一瞥;总结,...

    Miracle 评论0 收藏0
  • 全本 | iKcamp翻译 | 《JavaScript 轻量级函数编程》|《你不知道的JS》姊妹篇

    摘要:本书主要探索函数式编程的核心思想。我们在中应用的仅仅是一套基本的函数式编程概念的子集。我称之为轻量级函数式编程。通常来说,关于函数式编程的书籍都热衷于拓展阅读者的知识面,并企图覆盖更多的知识点。,本书统称为函数式编程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 译者团队(排名不分先后)...

    paney129 评论0 收藏0
  • 平时工作和学习中遇到的知识点(2)

    摘要:上传图片本地预览功能静态方法会创建一个,其中包含一个表示参数中给出的对象的。这个的生命周期和创建它的窗口中的绑定。这个新的对象表示指定的对象或对象。是对数组中每一项运行给定函数,如果该函数对任一项返回,则返回。 13、meta标签的用法 http://www.alenqi.site/2018/03/04/complete-tags/ 14、随机生...

    bladefury 评论0 收藏0
  • [译]Mixin 函数

    摘要:函数通常是面向对象编程风格,具有副作用。因为在函数式编程中,很有可能这些引用指向的并不是同一个对象。记住,函数并不意味着函数式编程。函数可以用函数式编程风格编写,避免副作用并不修改参数,但这并不保证。 软件构建系列 原文链接:Functional Mixins 译者注:在编程中,mixin 类似于一个固有名词,可以理解为混合或混入,通常不进行直译,本文也是同样。 这是软件构建系列教...

    zxhaaa 评论0 收藏0
  • 前端每周清单半年盘点之 JavaScript 篇

    摘要:前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。背后的故事本文是对于年之间世界发生的大事件的详细介绍,阐述了从提出到角力到流产的前世今生。 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热点、开发教程、工程实践、深度阅读、开源项目、巅峰人生等栏目。欢迎...

    Vixb 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<