Javascript偏函数与柯里化

到目前位置我们仅讨论绑定this,现在让我们更深入学习。
我们不仅能绑定this,也可以是参数,这较少使用,但有时很方便。

bind完整的语法为:

let bound = func.bind(context, arg1, arg2, ...);

可以绑定上下文this和函数的初始参数。举例,我们有个乘法函数mul(a,b):

function mul(a, b) {return a * b;
}

我们可以在该函数的基础上使用绑定创建一个double函数:

let double = mul.bind(null, 2);alert( double(3) ); // = mul(2, 3) = 6
alert( double(4) ); // = mul(2, 4) = 8
alert( double(5) ); // = mul(2, 5) = 10

调用mul.bind(null, 2)创建新函数double,传递调用mul函数,固定第一个参数上下文为null,第二个参数为2,多个参数传递也是如此。

这称为偏函数应用——我们创造一个新函数,让现有的一些参数值固定。

注意,这里确实不用this,但bind需要,所以必须使用null。

在下面代码中函数triple实现乘以3的功能:

let triple = mul.bind(null, 3);alert( triple(3) ); // = mul(3, 3) = 9
alert( triple(4) ); // = mul(3, 4) = 12
alert( triple(5) ); // = mul(3, 5) = 15

为什么我们通常使用偏函数?

这里我们偏函数的好处是:通过创建一个名称易懂的独立函数(double,triple),调用是无需每次传入第一个参数,因为第一个参数通过bind提供了固定值。

另一种使用偏函数情况是,当我们有一个很通用的函数,为了方便提供一个较常用的变体。

举例,我们有一个函数send(from, to, text),那么使用偏函数可以创建一个从当前用户发送的变体:sendTo(to, text)

使用没有上下文的偏函数

如果想固定一些参数,但不绑定this呢?

内置的bind不允许这样,我们不能忽略上下文并跳转到参数。幸运的是,可以仅绑定参数partial函数容易实现。

如下:

function partial(func, ...argsBound) {return function(...args) { // (*)return func.call(this, ...argsBound, ...args);}
}// Usage:
let user = {firstName: "John",say(time, phrase) {alert(`[${time}] ${this.firstName}: ${phrase}!`);}
};// add a partial method that says something now by fixing the first argument
user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());user.sayNow("Hello");
// Something like:
// [10:00] Hello, John!

调用partial(func[, arg1, arg2...])函数的结果为调用func的包装器(*号行):

  • this一致(因为user.sayNow是通过user调用的)
  • 然后给其...garsBound—— partial使用该参数("10:00")进行调用。
  • 然后提供参数...gars——提供给包装器的参数(“Hello“)

所以使用spread运算符很容易实现,是吗?
loadash库也提供了—.partial实现。

柯里化

有时人们混淆上面提及的偏函数和另一个名称为“柯里化”函数功能,柯里化是另一个有趣的处理函数技术,这里我们必须要涉及。

柯里化(Currying):转换一个调用函数f(a,b,c)f(a)(b)(c)方式调用。

让我们实现柯里化函数,执行一个两元参数函数,即转换f(a,b)f(a)(b):

function curry(func) {return function(a) {return function(b) {return func(a, b);};};
}// usage
function sum(a, b) {return a + b;
}let carriedSum = curry(sum);alert( carriedSum(1)(2) ); // 3

上面是通过一系列包装器实现的。

  • curry(func)的结果是function(a)的一个包装器。
  • 当调用sum(1)是,参数被保存在词法环境中,然后返回新的包装器function(b)
  • 然后sum(1)(2)提供2并最终调用function(b),然后传递调用给原始多参数函数sum

有一些柯里化的高级实现,如lodash库中_.curry可以实现更复杂功能。其返回一个包装器,它允许函数提供全部参数被正常调用或返回偏函数。

function curry(f) {return function(..args) {// if args.length == f.length (as many arguments as f has),//   then pass the call to f// otherwise return a partial function that fixes args as first arguments};
}

柯里化?应用场景?

高级柯里化允许函数正常调用,也可以容易以偏函数方式调用。为了理解其优势,我们需要一个实际的示例说明。

举例,我们有日志函数log(date,importance,message),格式化输出信息。实际项目中这些函数也有许多其他有用的特性,如:通过网络发送或过滤:

function log(date, importance, message) {alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}

让我们使用柯里化!

log = _.curry(log);

柯里化后仍然可以正常调用:log(new Date(), "DEBUG", "some debug");

我们也可以使用柯里化方式调用:log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)

这里定义一个便捷函数,记录当天日志:

// todayLog will be the partial of log with fixed first argument
let todayLog = log(new Date());// use it
todayLog("INFO", "message"); // [HH:mm] INFO message

现在再定义一个便捷函数:记录当天debug信息:

let todayDebug = todayLog("DEBUG");todayDebug("message"); // [HH:mm] DEBUG message

所以:

  1. 柯里化后没有失去任何东西,log仍然可以正常调用。
  2. 我们能生成在多个场景使用的便捷偏函数。

高级柯里化实现

如果你感兴趣,这里提供了上面提到的高级柯里化实现:

function curry(func) {return function curried(...args) {if (args.length >= func.length) {return func.apply(this, args);} else {return function(...args2) {return curried.apply(this, args.concat(args2));}}};}function sum(a, b, c) {return a + b + c;
}let curriedSum = curry(sum);// still callable normally
alert( curriedSum(1, 2, 3) ); // 6// get the partial with curried(1) and call it with 2 other arguments
alert( curriedSum(1)(2,3) ); // 6

这里实现看上去有点复杂,但确实很容易理解。curry(func)的结果是包装器curried,如下所示:

// func is the function to transform
function curried(...args) {if (args.length >= func.length) { // (1)return func.apply(this, args);} else {return function pass(...args2) { // (2)return curried.apply(this, args.concat(args2));}}
};

当我们运行时,有两个分支:

  1. 如果传递args数与原函数已经定义的参数个数一样或更长,那么直接调用。
  2. 获得偏函数:否则,不调用func函数,返回另一个包装器pass,提供连接之前的参数一起做为新参数重新应用curried。然后再次执行一个新调用,返回一个新偏函数(如果参数不够)或最终结果。

举例,让我们看sum(a, b, c)会怎样,三个参数,所以sum.length=3.

如果调用curried(1)(2)(3):

  1. 第一次调用curried(1),在词法环境中记住1,返回包装器pass
  2. 使用(2)调用包装器pass:其带着前面的参数(1),连接他们然后调用curried(1,2),因为参数数量仍然小于3,返回pass
  3. 再次使用(3)被调用包装器pass,带着之前的参数(1,2),然后增加3,并调用curried(1,2,3)——最终有三个参数,传递给原始函数。

如果仍然不清除,可以按顺序在脑子里或纸上跟踪调用过程。

仅针对函数参数长度固定
柯里化需要函数有已知的参数数量固定。

比柯里化多一点

根据柯里化定义,转换sum(a,b,c)sum(a)(b)(c).

但在Javascript中大多数实现是超越定义,也可以让函数使用多个参数变量执行。

总结

  • 当把已知函数的一些参数固定,结果函数被称为偏函数,通过使用bind获得偏函数,也有其他方式实现。

    当我们不想一次一次重复相同的参数时,偏函数是很便捷的。如我们有send(from,to)函数,如果from总是相同的,可以使用偏函数简化调用。

  • 柯里化是转换函数调用从f(a,b,c)f(a)(b)(c).Javascript通常既实现正常调用,也实现参数数量不足时的偏函数方式调用。

    当我们想容易的偏函数时,柯里化非常好。如我们已经看到的日志示例:通用的函数是log(date,importance,message),柯里化之后获得偏函数为,一个参数如log(date),或两个参数log(date,importance).

Javascript偏函数与柯里化相关推荐

  1. 【译】理解JavaScript中的柯里化

    译文开始 函数式编程是一种编程风格,这种编程风格就是试图将传递函数作为参数(即将作为回调函数)和返回一个函数,但没有函数副作用(函数副作用即会改变程序的状态). 有很多语言采用这种编程风格,其中包括J ...

  2. JavaScript 中函数 柯里化风格的运用

    导语 当我第一次看见 柯里化 这个词语的时候,我也表现出一脸懵,在代码程序中,看见这种 "高大上"的一些词汇叫法的时候,下意识的会觉得这个概念很难很深奥,但是当冷静下来,去深究过后 ...

  3. javascript --- 函数的柯里化 Vue 2.x中柯里化的使用

    函数式编程部分重点 参考资料: 函数式编程 柯里化 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数 var add = function (x) {return function(y ...

  4. JavaScript 优雅的 柯里化 转换函数

    Array.prototype.创建 = function 创建 () { var 局_参数列表 = []; for (var i = 0, j = arguments; i < j.lengt ...

  5. 【JavaScript】函数柯里化

    文章目录 1. 什么是函数柯里化 2. 柯里化常用场景 2.1 参数复用 2.2 提前返回 2.3 延迟执行 3. 经典例题 3.1 封装通用柯里化函数 3.2 创建一个灵活的多步执行的柯里化函数 3 ...

  6. 打造属于自己的underscore系列(五)- 偏函数和函数柯里化

    这一节的内容,主要针对javascript函数式编程的两个重要概念,偏函数(partial application) 和函数柯里化(curry)进行介绍.着重讲解underscore中对于偏函数应用的 ...

  7. 一文讲懂什么是函数柯里化,柯里化的目的及其代码实现

    柯里化(Currying) 柯里化(Currying)[1]是一种关于函数的高阶技术.它不仅被用于 JavaScript,还被用于其他编程语言. 柯里化是一种函数的转换,它是指将一个函数从可调用的 f ...

  8. 柯里化函数(Currying),什么是柯里化,为什么要进行柯里化,高级柯里化函数的实现

    柯里化(Currying) 柯里化(Currying)是一种关于函数的高阶技术.它不仅被用于 JavaScript,还被用于其他编程语言. 柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, ...

  9. 高级函数技巧-函数柯里化

    我们经常说在Javascript语言中,函数是"一等公民",它们本质上是十分简单和过程化的.可以利用函数,进行一些简单的数据处理,return 结果,或者有一些额外的功能,需要通过 ...

最新文章

  1. @NotNull JSR-303验证
  2. Android 四大组件之——Acitivity(二) 启动模式
  3. 索泰显卡超频软件测试要多少时间,显卡超频测试_索泰 GTX 760 2GD5 至尊OC+_显卡评测-中关村在线...
  4. JavaScript中十个一步拷贝数组的方法
  5. LiveVideoStackCon讲师热身分享 ( 十三 ) —— Intel QSV技术在FFmpeg中的实现与使用
  6. python学习笔记列表和元组(三)
  7. linux实现自己的write函数,Linux 内核源码阅读 - write 系统调用的实现
  8. mysql门派年龄最大的人_目前活着年龄最大的人
  9. 谷歌发布 MediaPipe Holistic,实现移动端同时进行人脸、手部和人体关键点检测跟踪...
  10. JS_高程3.基本概念(2)
  11. mysql数据库版本常用_推荐:经典版本常用MySQL的命令集锦_MySQL
  12. avalon使用笔记
  13. 千图成像python_吞了1000瓶老干妈的南山头铁鹅,Python制作千图成像(附上源代码和应用程序)...
  14. redis的lpush、rpush、rpop、lpop位置的简单理解
  15. 利用python制作动态二维码
  16. kindeditor 加载 html,为kindeditor编辑器添加“引用”(blockquote)标签
  17. 单片机万年历c语言程序设计,基于AT89C51单片机的多功能
  18. Windows11设置共享打印机
  19. 剑风2012年1月要出剧场版了,哇哈哈~~~~~
  20. Marquee标签实现走马灯效果

热门文章

  1. 要求按行读取成绩单。并在该行的后面加上该同学的总成绩
  2. 张益唐新成果首次公开直播,开场写下ac-bd=(a+b)c-(c+d)b,这回好像能看懂?
  3. 【大数据前置基础】Linux安装、Xshell远程控制、WinSCP文件传输
  4. PB byte 数据类型_大数据之脚踏实地学12Scala数据类型与运算符
  5. T1056点和正方形的关系 (信息学一本通C++)
  6. 布比李军:从难以接受到All In,我走过了一条怎样的路
  7. 重塑股份子公司重塑科技出席全国专精特新中小企业高峰论坛
  8. Docker系列之搭建Jenkins持续集成Maven项目
  9. git命令看一这篇就够了
  10. 鲸鱼优化算法优化BP神经网络回归预测的算法设计-附代码