函数柯里化 - 应用场景及实践

前言

函数柯里化:将多参简化为单参数的一种技术方式,其最终支持的是方法的连续调用,每次返回新的函数,在最终符合条件或者使用完所有的传参时终止函数调用

上面这个描述,如果没有接触过”柯里化“可能有点晦涩难懂,我来白话文讲解下。

一个函数会有N多个入参,比如sum(1, 2, 3, 4, 5, 6)

多参简化为单参数,也就是转换成sum(1)(2)(3)(4)(5)(6),同时这也实现了方法的连续调用,相当于

sum(1)
sum(2)
...
sum(6)

结合上述例子,那么这句每次返回新的函数,在最终符合条件或者使用完所有的传参时终止函数调用就很好理解了

也就是当符合条件(意味着你可以设置条件,上述例子没有设置,接下来会新起个对应的例子描述)

或者使用完所有的传参时终止函数调用,也就是说,当你对sum()这个函数实现了柯里化后,假设这个sum函数目前接收的入参只有6个,那么当你连续调用6次后,就该返回这6次的和了,而之前的调用都只会返回一个新的函数

应用场景

乍一看,这个柯里化似乎没想到什么非得的应用场景啊,没有应用场景,要这个技术有个啥用捏?

那么不妨看看下面几个应用例子

应用场景一 :小模块代码可重用 - 例子 购买某些商品,计算其价格

第一波迭代 商品价格 * 折扣

/**
*   @params price  单价
*   @params discount 折扣
*   @return finalPrice 最终价格 Number
*/
function getPrice(price, discount){
    return price * discount;
}

let productList = [{"price": 10, "discount": 0.4},{"price": 4,"discount": 0.2}]

let finalPriceArr = productList.map(item => getPrice(item.price, item.discount))

...

simple and easy

第二波迭代 折扣要进行某段复杂的业务逻辑计算,得出的才是最终的

(比如说折扣的数据源是外部的,大于0.9的当9折算,大于0.7小于等于0.9的当8折算什么的)

暂且认为我们已经抽离了折扣复杂计算的函数为discountComp()

此时正常逻辑我们会

  • 要么传入discount前,对discount经过discountComp()处理
  • 要么在getPrice函数里,对传入的discount进行处理

因为是迭代,getPrice这个函数可能被N多个地方调用(比如说代码里有100个地方用到了),那么最小的改动,我们应该是在getPrice()里调用discountComp()

/**
*  @params discount 折扣
*  @return finalDiscount 最终折扣 Number
*/
function discountComp(discount) {
    //  贼拉复杂的计算
}
/**
*   @params price  单价
*   @params discount 折扣
*   @return finalPrice 最终价格 Number
*/
function getPrice(price, discount){
    const finalDiscount = discountComp(discount)
    return price * finalDiscount;
}

let productList = [{"price": 10, "discount": 0.4},{"price": 4,"discount": 0.2}]

let finalPriceArr = productList.map(item => getPrice(item.price, item.discount))
...

一切都很完美,对吧,除了每次getPrice都得进行一次复杂计算,好吧,虽然我知道,但时间紧交差了(工作中绝对有过这种,对吧?)
然后就会有下面的杯具了

第三波迭代 全场一律五折促销,且折扣的复杂计算加深了,要调用某个外部数据源的接口,获得某个系数值,再进行复杂计算

再看回现有的代码,卧槽?那岂不是每一次getPrice我都要调一次接口?有个商品列表,100个商品我得调100次?这直接购物车计算就卡爆了好嘛。

当然还是有除了“柯里化”这一招以外能破的方法的,因为是常量系数,提前获取了定义为全局常量然后再进行discountComp函数就可以了,或者说给getPrice和discountComp加参,该参数接收接口获取到的常量系数,但是弊端也明显,要写、要改的代码想想就不少,在此不做赘述

用柯里化思维解决这个需求

/**
*  @params discount 折扣
*  @return finalDiscount 最终折扣 Number
*/
function discountComp(discount) {
    /*  贼拉复杂的计算, 还有API请求 */
}
/**
*   @params price  单价
*   @params discount 折扣
*   @return finalPrice 最终价格 Number
*/
function getPrice(price, discount){
    return price * finalDiscount;
}

function curriedGetPrice(discount){
  const finalDiscount = discountComp(discount)
  return price => getPrice(price, finalDiscount)
}

let productList = [{"price": 10, "discount": 0.4},{"price": 4,"discount": 0.2}]

let afterDiscountComp = curriedGetPrice(0.5)
let finalPriceArr = productList.map(item => afterDiscountComp(item.price))
...

这个场景,要杠的话还是有可杠之处的,将就着看吧

应用场景二:不定参数累加

很好理解,实现如下:
add(1)(2)(3) = 6
add(1, 2, 3)(4) = 10
add(1)(2)(3)(4)(5) = 15

其实就是柯里化后的add()

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    console.log("arguments", arguments)

    var _args = [].slice.call(arguments);
    console.log("_args", _args)
    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值,执行时已经收集所有参数为数组
    var adder = function () {
        var _adder = function() {
          // 执行收集动作,每次传入的参数都累加到原参数
            [].push.apply(_args, [].slice.call(arguments));
            return _adder;
        };
        // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
        _adder.toString = function () {
            return _args.reduce(function (a, b) {
                return a + b;
            });
        }
        return _adder;
    }
    return adder(_args);
}

要读懂上面的代码,也是需要一定了解的

先是:
[].slice.call(arguments)

arguments对象是所有(非箭头)函数中都可用的局部变量
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments

[].slice.call()
先要知道slice()
介绍:slice() 方法返回一个新的数组对象,这一对象是一个由 beginend决定的原数组的浅拷贝(包括begin,不包括end)。原始数组不会被改变
入参:

image.png

这里其实还涉及”浅拷贝“的知识,这就与”引用“有关系了
image.png

简单来说,如果数组里是对象,那么slice会复制该对象的引用,此时如果对象本身的属性修改了,那么slice后的数组里的对象引用所指向的对象也会被修改,这一点对于普通的字符串、数字、布尔值来说,不适用,这三种将直接复制
然后就是
image.png

然后是[].push.apply

image.png

相当于合并数组了;

再然后是_args.reduce也即是Array.reduce

image.png

再再然后便是_adder.toString,这里重写了_adder函数的toString方法

这有个默认规则,当我们定义了test函数,直接console.log(test),相当于执行该函数的默认toString方法,即返回函数代码字符串。

此处重写,当为add(1, 2),执行的是_adder的toString方法,而当为add(1,2)(4,5),此时用到了匿名函数立即执行的语法,相当于add(1,2)执行完,得到了_adder函数,此时立即执行_adder(4,5),则不会走toString先,而是执行定义好的_adder函数,而这个函数进行了[].push.apply(_args, [].slice.call(arguments));就很好理解了,最终又返回了个_adder函数,但是此时_args已经累加了第二个(4,5),最终得到的还是_adder函数,所以最终执行了_adder的toString方法

再通俗讲:

当为add(1,2)只执行了_adder被重写后的toString方法
当为add(1,2)(3)...,两个甚至更多立即执行的函数,则执行对应次数的_adder方法后,最终执行_adder被重写后的toString方法

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容