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. 【职场】聊聊P5晋升P6之后
  2. 使用 ASP.NET Core, Entity Framework Core 和 ABP 创建N层Web应用 第二篇
  3. 【ArcGIS微课1000例】0012:ArcGIS创建及连接ArcSDE企业级地理数据库实例
  4. servlet 调用oracle数据库存储过程
  5. Java NIO原理图文分析及代码实现
  6. “价值互联网”时代,带你读懂区块链
  7. TranslateAnimation
  8. python发送QQ邮件
  9. 下载神器aria2和他的客户端Persepolis
  10. 电商短视频运营应该怎么做
  11. IDEA 2018.3.3 有效期至 2100
  12. sd卡U盘异常检测程序
  13. Android Qcom USB Driver学习(八)
  14. 一起学java-韩顺平老师
  15. 30.一张图理解EOS是什么
  16. 油气管道供应可视化数据大屏:连点成线,打破信息孤岛
  17. butter滤波器是iir吗_MATLAB IIR滤波器设计函数buttord与butter
  18. 数值分析实习作业(各种插值函数与积分公式的python代码实现)
  19. WinPE_USB启动盘制作
  20. 37来电号码归属地的显示

热门文章

  1. Unity中常用的几种设计模式
  2. html/jsp 让背景图片铺满整个网页
  3. win10系统可以做补丁服务器,关于win10从wsus服务器更新补丁的问题
  4. Live回顾 | 同盾曾谁飞:智能语音技术在金融风控的应用及展望...
  5. 【学习笔记】FTP创建用户并指定用户主目录
  6. java计算机毕业设计网上鲜花店网站源码+数据库+lw文档+系统+部署
  7. 第三十八章 变态级怪物
  8. 我的三年软件测试之路----金阳光老师自传
  9. Vue安装和环境配置
  10. Python统计词频的几种方法