十、详解函数柯里【下】

简介: 柯里化是函数的一个高级应用,想要理解它并不简单。因此我一直在思考应该如何更加表达才能让大家理解起来更加容易。通过上一个章节的学习我们知道,接收函数作为参数的函数,都可以叫做高阶函数。我们常常利用高阶函数来封装一些公共的逻辑。这一章我们要学习的柯里化,其实就是高阶函数的一种特殊用法。


额外知识补充


无限参数的柯里化。


该部分内容可忽略


在前端面试中,你可能会遇到这样一个涉及到柯里化的题目。


// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;


这个题目的目的是想让add执行之后返回一个函数能够继续执行,最终运算的结果是所有出现过的参数之和。而这个题目的难点则在于参数的不固定。我们不知道函数会执行几次。因此我们不能使用上面我们封装的createCurry的通用公式来转换一个柯里化函数。只能自己封装,那么怎么办呢?在此之前,补充2个非常重要的知识点。


一个是ES6函数的不定参数。假如我们有一个数组,希望把这个数组中所有的子项展开传递给一个函数作为参数。那么我们应该怎么做?



// 大家可以思考一下,如果将args数组的子项展开作为add的参数传入
function add(a, b, c, d) {
    return a + b + c + d;
}
var args = [1, 3, 100, 1];


在ES5中,我们可以借助之前学过的apply来达到我们的目的。


add.apply(null, args);  // 105


而在ES6中,提供了一种新的语法来解决这个问题,那就是不定参。写法如下:


add(...args);  // 105


这两种写法是等效的。OK,先记在这里。在接下的实现中,我们会用到不定参数的特性。


第二个要补充的知识点是函数的隐式转换。当我们直接将函数参与其他的计算时,函数会默认调用toString方法,直接将函数体转换为字符串参与计算。


function fn() { return 20 }
console.log(fn + 10);     // 输出结果 function fn() { return 20 }10


我们可以重写函数的toString方法,让函数参与计算时,输出我们想要的结果。


function fn() { return 20; }
fn.toString = function() { return 30 }
console.log(fn + 10); // 40


除此之外,当我们重写函数的valueOf方法也能够改变函数的隐式转换结果。


function fn() { return 20; }
fn.valueOf = function() { return 60 }
console.log(fn + 10); // 70


当我们同时重写函数的toString方法与valueOf方法时,最终的结果会取valueOf方法的返回结果。


function fn() { return 20; }
fn.valueOf = function() { return 50 }
fn.toString = function() { return 30 }
console.log(fn + 10); // 60


补充了这两个知识点之后,我们可以来尝试完成之前的题目了。add方法的实现仍然会是一个参数的收集过程。当add函数执行到最后时,仍然返回的是一个函数,但是我们可以通过定义toString/valueOf的方式,让这个函数可以直接参与计算,并且转换的结果是我们想要的。而且它本身也仍然可以继续执行接收新的参数。实现方式如下。


function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = [].slice.call(arguments);
    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var adder = function () {
        var _adder = function() {
            // [].push.apply(_args, [].slice.call(arguments));
            _args.push(...arguments);
            return _adder;
        };
        // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
        _adder.toString = function () {
            return _args.reduce(function (a, b) {
                return a + b;
            });
        }
        return _adder;
    }
    // return adder.apply(null, _args);
    return adder(..._args);
}
var a = add(1)(2)(3)(4);   // f 10
var b = add(1, 2, 3, 4);   // f 10
var c = add(1, 2)(3, 4);   // f 10
var d = add(1, 2, 3)(4);   // f 10
// 可以利用隐式转换的特性参与计算
console.log(a + 10); // 20
console.log(b + 20); // 30
console.log(c + 30); // 40
console.log(d + 40); // 50
// 也可以继续传入参数,得到的结果再次利用隐式转换参与计算
console.log(a(10) + 100);  // 120
console.log(b(10) + 100);  // 120
console.log(c(10) + 100);  // 120
console.log(d(10) + 100);  // 120
// 其实上栗中的add方法,就是下面这个函数的柯里化函数,只不过我们并没有使用通用式来转化,而是自己封装
function add(...args) {
    return args.reduce((a, b) => a + b);
}


相关文章
【数值分析】二分法求方程的根(附matlab代码)
【数值分析】二分法求方程的根(附matlab代码)
|
XML 开发框架 安全
中间件常见漏洞之JBOSS
中间件常见漏洞之JBOSS
3804 0
|
设计模式 Java 黑灰产治理
java设计模式---代理模式详解
java设计模式---代理模式详解
269 0
|
Web App开发 域名解析 缓存
万字+图片解析计算机网络应用层
万字+图片解析计算机网络应用层
万字+图片解析计算机网络应用层
|
存储 运维 Kubernetes
大规模 K8s 集群管理经验分享 · 上篇
11 月 23 日,Erda 与 OSCHINA 社区联手发起了【高手问答第 271 期 -- 聊聊大规模 K8s 集群管理】,目前问答活动已持续一周,由 Erda SRE 团队负责人骆冰利为大家解答,以下是本次活动的部分问题整理合集,其他问题也将于近期整理后发布,敬请期待!
904 0
大规模 K8s 集群管理经验分享 · 上篇
|
存储 安全 机器人
简化业务流程 — 低代码助力初创公司快速发展
这是一份指南,以解释为什么简化业务运作是当务之急,以及企业如何能够在其中取得成功。
420 0
简化业务流程 — 低代码助力初创公司快速发展
|
分布式计算 Hadoop Windows
hadoop搭建之hadoop连接eclipse
在进行本文操作之前,确定已经安装好了jdk,eclipse,hadoop,所有环境都配置好了
2916 0
|
Web App开发 JavaScript 前端开发
使用 HTML5, javascript, webrtc, websockets, Jetty 和 OpenCV 实现基于 Web 的人脸识别
这是一篇国外的文章,介绍如何通过 WebRTC、OpenCV 和 WebSocket 技术实现在 Web 浏览器上的人脸识别,架构在 Jetty 之上。 实现的效果包括: 还能识别眼睛 人脸识别的核心代码: 页面: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15...
2274 0