高阶函数应用 —— 柯里化与反柯里化

JavaScript中的闭包与高阶函数详解 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。var a = 2return barbaz() // 输出: 2foo 函数传递出了一个函数 bar,传递出来的 bar 被赋值给 baz 并调用,虽然这时 baz 是在 foo 作用域外执行的,但 baz 在调用的时候可以访问到前面的 bar 函数所在的 foo 的内部作用域。 阅读详情

在这里插入图片描述


阅读原文


前言

在 JavaScript 中,柯里化和反柯里化是高阶函数的一种应用,在这之前我们应该清楚什么是高阶函数,通俗的说,函数可以作为参数传递到函数中,这个作为参数的函数叫回调函数,而拥有这个参数的函数就是高阶函数,回调函数在高阶函数中调用并传递相应的参数,在高阶函数执行时,由于回调函数的内部逻辑不同,高阶函数的执行结果也不同,非常灵活,也被叫做函数式编程。


柯里化

在 JavaScript 中,函数柯里化是函数式编程的重要思想,也是高阶函数中一个重要的应用,其含义是给函数分步传递参数,每次传递部分参数,并返回一个更具体的函数接收剩下的参数,这中间可嵌套多层这样的接收部分参数的函数,直至返回最后结果。

1、最基本的柯里化拆分

// 柯里化拆分
// 原函数
function add(a, b, c) {
    return a + b + c;
}

// 柯里化函数
function addCurrying(a) {
    return function (b) {
        return function (c) {
            return a + b + c;
        }
    }
}

// 调用原函数
add(1, 2, 3); // 6

// 调用柯里化函数
addCurrying(1)(2)(3) // 6

被柯里化的函数 addCurrying 每次的返回值都为一个函数,并使用下一个参数作为形参,直到三个参数都被传入后,返回的最后一个函数内部执行求和操作,其实是充分的利用了闭包的特性来实现的。

2、柯里化通用式

上面的柯里化函数没涉及到高阶函数,也不具备通用性,无法转换形参个数任意或未知的函数,我们接下来封装一个通用的柯里化转换函数,可以将任意函数转换成柯里化。

// 柯里化通用式 ES5
function currying(func, args) {
    // 形参个数
    var arity = func.length;
    // 上一次传入的参数
    var args = args || [];

    return function () {
        // 将参数转化为数组
        var _args = [].slice.call(arguments);

        // 将上次的参数与当前参数进行组合并修正传参顺序
        Array.prototype.unshift.apply(_args, args);

        // 如果参数不够,返回闭包函数继续收集参数
        if(_args.length < arity) {
            return currying.call(null, func, _args);
        }

        // 参数够了则直接执行被转化的函数
        return func.apply(null, _args);
    }
}

上面主要使用的是 ES5 的语法来实现,大量的使用了 call 和 apply,下面我们通过 ES6 的方式实现功能完全相同的柯里化转换通用式。

// 柯里化通用式 ES6
function currying(func, args = []) {
    let arity = func.length;

    return function (..._args) {
        _args.unshift(...args);

        if(_args.length < arity) {
            return currying(func, _args);
        }

        return func(..._args);
    }
}

函数 currying 算是比较高级的转换柯里化的通用式,可以随意拆分参数,假设一个被转换的函数有多个形参,我们可以在任意环节传入任意个数的参数进行拆分,举一个例子,假如 5 个参数,第一次可以传入 2 个,第二次可以传入 1 个, 第三次可以传入剩下的,也有其他的多种传参和拆分方案,因为在 currying 内部收集参数的同时按照被转换函数的形参顺序进行了更正。

柯里化的一个很大的好处是可以帮助我们基于一个被转换函数,通过对参数的拆分实现不同功能的函数,如下面的例子。

// 柯里化通用式应用 —— 普通函数
// 被转换函数,用于检测传入的字符串是否符合正则表达式
function checkFun(reg, str) {
    return reg.test(str);
}

// 转换柯里化
const check = currying(checkFun);

// 产生新的功能函数
const checkPhone = check(/^1[34578]\d{9}$/);
const checkEmail = check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);

上面的例子根据一个被转换的函数通过转换变成柯里化函数,并用 check 变量接收,以后每次调用 check 传递不同的正则就会产生一个检测不同类型字符串的功能函数。

这种使用方式同样适用于被转换函数是高阶函数的情况,比如下面的例子。

// 柯里化通用式应用 —— 高阶函数
// 被转换函数,按照传入的回调函数对传入的数组进行映射
function mapFun(func, array) {
    return array.map(func);
}

// 转换柯里化
const getNewArray = currying(mapFun);

// 产生新的功能函数
const createPercentArr = getNewArray(item => `${item * 100}%`);
const createDoubleArr = getNewArray(item => item * 2);

// 使用新的功能函数
let arr = [1, 2, 3, 4, 5];
let percentArr = createPercentArr(arr); // ['100%', '200%', '300%', '400%', '500%',]
let doubleArr = createDoubleArr(arr); // [2, 4, 6, 8, 10]

3、柯里化与 bind

bind 方法是经常使用的一个方法,它的作用是帮我们将调用 bind 函数内部的上下文对象 this 替换成我们传递的第一个参数,并将后面其他的参数作为调用 bind 函数的参数。

// bind 方法原理模拟
// bind 方法的模拟
Function.prototype.bind = function (context) {
    var self = this;
    var args = [].slice.call(arguments, 1);

    return function () {
        return self.apply(context, args);
    }
}

通过上面代码可以看出,其实 bind 方法就是一个柯里化转换函数,将调用 bind 方法的函数进行转换,即通过闭包返回一个柯里化函数,执行该柯里化函数的时候,借用 apply 将调用 bind 的函数的执行上下文转换成了 context 并执行,只是这个转换函数没有那么复杂,没有进行参数拆分,而是函数在调用的时候传入了所有的参数。


反柯里化

反柯里化的思想与柯里化正好相反,如果说柯里化的过程是将函数拆分成功能更具体化的函数,那反柯里化的作用则在于扩大函数的适用性,使本来作为特定对象所拥有的功能函数可以被任意对象所使用。

1、反柯里化通用式

反柯里化通用式的参数为一个希望可以被其他对象调用的方法或函数,通过调用通用式返回一个函数,这个函数的第一个参数为要执行方法的对象,后面的参数为执行这个方法时需要传递的参数。

// 反柯里化通用式 ES5
function uncurring(fn) {
    return function () {
        // 取出要执行 fn 方法的对象,同时从 arguments 中删除
        var obj = [].shift.call(arguments);
        return fn.apply(obj, arguments);
    }
}
// 反柯里化通用式 ES6
function uncurring(fn) {
    return function (...args) {
        return fn.call(...args);
    }
}

下面我们通过一个例子来感受一下反柯里化的应用。

// 反柯里化通用式应用
// 构造函数 F
function F() {}

// 拼接属性值的方法
F.prototype.concatProps = function () {
    let args = Array.from(arguments);
    return args.reduce((prev, next) => `${this[prev]}&${this[next]}`);
}

// 使用 concatProps 的对象
let obj = {
    name: "Panda",
    age: 16
};

// 使用反柯里化进行转化
const concatProps = uncurring(F.prototype.concatProps);

concatProps(obj, "name", "age"); // Panda&16

反柯里化还有另外一个应用,用来代替直接使用 call 和 apply,比如检测数据类型的 Object.prototype.toString 等方法,以往我们使用时是在这个方法后面直接调用 call 更改上下文并传参,如果项目中多处需要对不同的数据类型进行验证是很麻的,常规的解决方案是封装成一个检测数据类型的模块。

// 检测数据类型常规方案
function checkType(val) {
    return Object.prototype.toString.call(val);
}

如果需要这样封装的功能很多就麻烦了,代码量也会随之增大,其实我们也可以使用另一种解决方案,就是利用反柯里化通用式将这个函数转换并将返回的函数用变量接收,这样我们只需要封装一个 uncurring 通用式就可以了。

// 反柯里化创建检测类型函数
const checkType = uncurring(Object.prototype.toString);

checkType(1); // [object Number]
checkType("hello"); // [object String]
checkType(true); // [object Boolean]

2、通过函数调用生成反柯里化函数

在 JavaScript 我们经常使用面向对象的编程方式,在两个类或构造函数之间建立联系实现继承,如果我们对继承的需求仅仅是希望一个构造函数的实例能够使用另一个构造函数原型上的方法,那进行繁琐的继承很浪费,简单的继承父子类的关系又不那么的优雅,还不如之间不存在联系。

// 将反柯里化方法扩展到函数原型
Function.prototype.uncurring = function () {
    var self = this;
    return function () {
        return Function.prototype.call.apply(self, arguments);
    }
}

之前的问题通过上面给函数扩展的 uncurring 方法完全得到了解决,比如下面的例子。

// 函数应用反柯里化原型方法
// 构造函数
function F() {}

F.prototype.sayHi = function () {
    return "I'm " + this.name + ", " + this.age + " years old.";
}

// 希望 sayHi 方法被任何对象使用
sayHi = F.prototype.sayHi.uncurring();

sayHi({ name: "Panda", age: 20}); // I'm Panda, 20 years old.

在 Function 的原型对象上扩展的 uncurring 中,难点是理解 Function.prototype.call.apply,我们知道在 call 的源码逻辑中 this 指的是调用它的函数,在 call 内部用第一个参数替换了这个函数中的 this,其余作为形参执行了函数。

而在 Function.prototype.call.apply 中 apply 的第一个参数更换了 call 中的 this,这个用于更换 this 的就是例子中调用 uncurring 的方法 F.prototype.sayHi,所以等同于 F.prototype.sayHi.call,arguments 内的参数会传入 call 中,而 arguments 的第一项正是用于修改 F.prototype.sayHi 中 this 的对象。


总结

看到这里你应该对柯里化和反柯里化有了一个初步的认识了,但要熟练的运用在开发中,还需要我们更深入的去了解它们内在的含义。


浅谈JS中的反柯里化( uncurrying) 反柯里化 相反,反柯里化的作用在与扩大函数的适用性,使本来作为特定对象所拥有的功能的函数可以被任意对象所用. 即把如下给定的函数签名, obj.func(arg1, arg2) 转化成一个函数形式,签名如下: func(obj, arg1, arg2) 这就是 反柯里化的形式化描述。 例如,下面的一个简单实现: Function.prototype.uncurrying = function() { var that = this; return function() { return Function.prototype.call.apply(that, argum 立即下载

相关推荐

深度学习---双层神经网络中矩阵的运算及其含义记录

深度学习---双层神经网络中矩阵的运算及其含义记录神经网络的正向传播与反向传播正向传播第一层的权重矩阵第一层的输入矩阵Z^1^ = W^1^X^1^ + b^1^A^1^ = σ(Z^1^)第二层的权重矩阵第二层的输入矩阵Z^2^ = W^2^A^1^ + b^2^反向传播dz^2^ = A^2^ - Ydw^2^ = 1/n dz^2^A^1T^db^2^ = 1/n np.sum(dz^2...

qq_43228441的博客 3746

函数柯里化

前面的话 高阶函数的一个高级应用就是函数柯里化。这篇文章介绍函数柯里化。 柯里化定义 函数柯里化(function currying),又称部分求值。一个currying函数首先会接受一些参数,接受这些参数之后,该函数并不会立即求值,而是继续返回一个新的函数,刚才传入的参数在函数形成闭包中保存起来。等到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。 实际应用: 延迟计算 动态...

小柒的前端之旅 665

手把手配置:用Synopsys VDK为英飞凌TC4x系列MCU做虚拟原型开发

本文详细介绍了如何使用Synopsys VDK为英飞凌TC4x系列MCU进行虚拟原型开发,涵盖开发环境搭建、关键外设虚拟化配置、AUTOSAR栈集成及虚拟调试与性能调优全流程。通过虚拟原型技术,开发者可提前6-12个月启动软件开发,显著提升汽车电子项目的开发效率。

weixin_30606461的博客 291

函数柯里化的原理及简单实现

什么是函数柯里化? 概念:函数柯里化就是把一个具有n个参数的函数转变成为可以进行二次甚至n次调用执行的函数,也就是说将函数的n个参数分1~n次传入,在n个参数传入完成后一次性执行这个函数。 原理:函数柯里化的原理就是将要柯里化的函数作为柯里化标准函数的返回值,将要保存的内容作为外部函数的变量。也就是利用闭包创建一个私有域,使内部变量可以长期保存在内存中,直到内部返回的函数执行完成。(也就是要被柯里化的函数的n个参数都传入完成,一次性完成了计算操作,那么闭包中保存的变量就被释放) 以一个简单的例子来说:

cute_ming的博客 987

电机控制实战:SVPWM五段式与七段式算法在STM32上的实现对比

本文深入对比了SVPWM五段式与七段式算法在STM32上的实现效果,详细分析了两种算法在占空比控制、开关损耗和波形质量等方面的差异。通过实测数据,为工程师提供了针对不同应用场景的选型建议,包括电池供电设备和高精度伺服控制系统的优化方案。

h6i7j8的博客 584

TRAE智能体开发:TRAE概述

TRAE(/treɪ/)深度融合 AI 能力,是一名能够理解需求、调用工具并独立完成各类开发任务的“AI 开发工程师”,帮助你高效推进每一个项目。

王国平的博客 1267

实现一个通用的函数柯里化的函数

这篇文章会一步一步带你实现一个通用的函数柯里化的函数,即使你完全不懂柯里化这个概念也没关系,我会先介绍柯里化函数的概念,然后以**实现一个完整的判断变量类型的函数**的例子来让你直观体验函数柯里化,然后再实现一个通用的函数柯里化函数。

Curry_On的博客 938

【AUTOSAR-E2E】-2.2-配置E2E:基于E2EPW配置一个TX发送的Signal Group(使用E2E Profile 2)

《共13页精讲》:此法也适用于E2E Profile 4/5/6/7/11/22 目录 1 检查CAN Matrix Arxml/DBC是否满足E2E需求 2 在DaVinci Developer中为Port添加E2E保护 3 在DaVinci Configurator settings设置 4 如何使用应用层的E2E保护 5 结尾 <--返回「Autosar_BSW高阶配置」总目录--> <--返回「Autosar_BSW高阶配置」专栏主页--> &lt.

「汽车电子助手」的博客 4218

前端进阶之---柯里化【函数式编程】

前端进阶之---柯里化

南北极之间 2772

DDR5中的On-Die Termination(ODT)详解:电阻值支持及其背后的秘密

DDR5支持多档ODT电阻值,范围从40Ω到480Ω,均基于240Ω标准参考电阻RZQR_{ZQ}RZQ​。不同电阻档位对应不同的信号负载和功耗需求。校准电压点关键,常用0.8×VDD进行精准调校。CA信号间ODT匹配必须严格控制,保证命令总线稳定。选择合适的ODT值是高速内存设计中不可忽视的细节。

weixin_43199439的博客 2099

OpenClaw实战系列04:Jira/Confluence深度集成实战指南

汇总 Confluence 空间 TEAM 下所有关于“性能优化”的文档,输出要点自然语言创建和查询 Jira 任务,无需离开聊天工具基于 Confluence 的私有知识库问答,让 AI 真正理解团队文档Jira 与 Confluence 联动,打通项目管理与知识管理定时同步与通知,自动化团队信息流转至此,你的 OpenClaw AI 助理已经不仅是一个聊天机器人,而是真正融入团队工作流的“智能项目管理助手”。下一期我们将带来OpenClaw 自定义技能开发实战。

PeterPan的博客 572

python 函数式 panda_高阶函数应用 —— 柯里化与反柯里化

前言在 JavaScript 中,柯里化和反柯里化是高阶函数的一种应用,在这之前我们应该清楚什么是高阶函数,通俗的说,函数可以作为参数传递到函数中,这个作为参数的函数叫回调函数,而拥有这个参数的函数就是高阶函数,回调函数在高阶函数中调用并传递相应的参数,在高阶函数执行时,由于回调函数的内部逻辑不同,高阶函数的执行结果也不同,非常灵活,也被叫做函数式编程。柯里化在 JavaScript 中,函数柯里...

weixin_39713686的博客 188

【OpenClaw企业级智能体实战】第10篇:医疗行业OpenClaw实战:病历自动书写、医保智能审核与患者随访管理(附HIPAA合规配置+完整代码)

医疗行业正面临“效率提升需求”与“合规监管红线”的双重矛盾:病历书写占据医生30%-50%工作时间,医保审核效率低下,患者随访工作量巨大,但医疗数据“不出域”“责任不转移”的要求又限制了AI落地。本文基于华为云、腾讯云安全规范及中关村科金、复旦大学附属中山医院等真实实践,拆解OpenClaw在医疗场景的五大核心应用(病历自动书写、医保智能审核、患者随访管理、科研文献综述、医学影像预处理),提供私有化部署架构、HIPAA合规配置模板、完整实操流程及可直接运行的代码。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 440

js高阶函数应用—函数柯里化和反柯里化

在Lambda演算(一套数理逻辑的形式系统,具体我也没深入研究过)中有个小技巧:假如一个函数只能收一个参数,那么这个函数怎么实现加法呢,因为高阶函数是可以当参数传递和返回值的,所以问题就简化为:写一个只有一个参数的函数,而这个函数返回一个带参数的函数,这样就实现了能写两个参数的函数了(具体参见下边代码)——这就是所谓的柯里化(Currying,以逻辑学家Hsakell Curry命名),...

weixin_30567471的博客 968

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

4张图带你感受高阶函数的应用

展示高阶函数的应用,四张图带你了解部分应用,柯里化,反柯里化,组合。

yoongrui 611

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

高阶函数全解析(定义、应用 -- 函数柯理化 反柯理化 发布订阅模式 观察者模式)

什么是高阶函数?满足下面两点中的任意一点都是高阶函数:如果一个函数的参数是一个函数,那么当前这个函数也是一个高阶函数。如果一个函数返回一个函数,那么当前这个函数也是一个高阶函数。

zgynbnbnb的博客 1273

深入浅出 高阶函数 ☕️

深入浅出 高阶函数 ☕️  看到本文的标题,应该有很多小伙伴看到 高阶函数 这个词语时, 心里可能会觉得很高大上, 其实很普通, 你可能接触过很多次了, 只不过可能你不知道这就是高阶函数. 那么何为 “高阶函数” 呢? 函数的形参如果可以为函数,又或者函数的返回值的一个函数式,这个函数就可以称之为 “高阶函数”. 也就是说, 高阶函数必须满足以下要求之一 函数的形参可以为函数 ; 函数的返回值可...

qq_40181779的博客 280

【javaScript核心】高阶函数

JavaScript 中,函数是一等公民」,在各种书籍和文章中我们总能看到这句话。通俗地解释就是:JS 中的函数也是对象,可以有属性,可以赋值给一个变量,可以放在数组里作为元素,可以作为其他对象的属性,普通对象能做的它能做,普通对象不能做的它也能做。而所谓的高阶函数(Higher-order function),就是输入参数里有函数,或者输出是函数的函数。最常见的高阶函数有map()reduce()filter()sort()setTimeout。

coder 214

高阶函数的常见应用场景与实现

高阶函数是 JavaScript 中非常强大的工具,它们可以接收函数作为参数,或者返回一个函数。本文将介绍几种常见的高阶函数应用场景,包括柯里化、反柯里化、函数节流、分时函数和惰性加载函数,并提供具体的实现代码。柯里化是一种将多参数函数转换为一系列单参数函数的技术。它的核心思想是部分求值,即函数在接收参数时不会立即计算,而是累积参数,直到需要计算结果时才一次性计算。 应用场景: 参数分步传递,延迟计算。 复用部分参数,生成新的函数。 2. 反柯里化(Uncurrying) 反柯里化是将只能应用于特定对

罐装旺仔的博客院子 618

高阶函数不会用?教你JS中最实用的几个高阶函数用法

作者:chenhongdonghttps://juejin.im/post/5ad6b34a6fb9a028cc61bfb3不废话,先来看下什么是高阶函数高阶函数函数可以作为参数传递函数...

前端达人 1022

js中问号的用法_高阶函数不会用?教你JS中最实用的几个高阶函数用法

不废话,先来看下什么是高阶函数高阶函数函数可以作为参数传递函数可以作为返回值输出函数作为参数传递回调函数在ajax异步请求的过程中,回调函数使用的非常频繁在不确定请求返回的时间时,将callback回调函数当成参数传入待请求完成后执行callback函数下面看个简单的demo:说实在的本来只是个简单的 ,不过越写越兴奋,就弄成了个小demo了,大家也可以copy下去自己添油加醋一下(写成各种版本)...

weixin_42513038的博客 618
上一篇: Canvas + WebSocket + Redis 实现一个视频弹幕
下一篇: CommonJS 模块化简易实现
Panda Shen
博客等级 码龄10年 18粉丝 50原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值