理解JS里的偏函数与柯里化
联系到上篇博客讲的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, );
alert( double() ); // = mul(2, 3) = 6
调用mul.bind(null, 2)
创建新函数double
,传递调用mul
函数,固定第一个参数上下文为null,第二个参数为2,多个参数传递也是如此。
这称为偏函数应用——我们创造一个新函数,让现有的一些参数值固定。
注意,这里确实不用this,但bind需要,所以必须使用null。
为什么我们通常使用偏函数?
这里我们偏函数的好处是:
(1)通过创建一个名称易懂的独立函数(double,triple等),调用时无需每次传入第一个参数,因为第一个参数通过bind提供了固定值。
(2)另一种使用偏函数情况是,当我们有一个很通用的函数,为了方便提供一个较常用的变体。举例,我们有一个函数send(from, to, text)
,那么使用偏函数可以创建一个从当前用户发送的变体:sendTo(to, text)
偏函数与柯里化定义:
维基百科中对偏函数 (Partial application) 的定义为:
In computer science, partial application (or partial function application)
refers to the process of fixing a number of arguments to a function,
producing another function of smaller arity.
翻译成中文:在计算机科学中,局部应用是指固定一个函数的一些参数,然后产生另一个更小元的函数。(什么是元?元是指函数参数的个数,比如一个带有两个参数的函数被称为二元函数。)
维基百科中对柯里化 (Currying) 的定义为:
In mathematics and computer science,
currying is the technique of translating the evaluation of a function
that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions,
each with a single argument.
翻译成中文:在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
偏函数与柯里化区别:
柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。
局部应用则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。
使用没有上下文的偏函数
bind可以实现偏函数应用,但是如果想固定一些参数,但不绑定this呢?
内置的bind
不允许这样,我们不能忽略上下文并跳转到参数。幸运的是,可以仅绑定参数partial
函数容易实现。如下:
function partial(func, ...argsBound) {
return function(...args) {
return func.call(this, ...argsBound, ...args);
}
} let user = {
firstName: "John",
say(time, phrase) {
alert(`[${time}] ${this.firstName}: ${phrase}!`);
}
}; // 偏函数,绑定第一个参数,say的time
user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());
//调用新函数提供第二个参数phrase
user.sayNow("Hello");
// [10:00] Hello, John!
调用partial(func[, arg1, arg2...])
函数的结果为调用func
的包装器(即第一个return的函数):
(1)this一致(因为user.sayNow
是通过user
调用的)
(2)然后给其...argsBound
—— partial
使用该参数("10:00"
)进行调用。
(3)然后提供参数...args
——提供给包装器的参数(“Hello
“)
所以使用spread运算符很容易实现。
柯里化实现
有时人们混淆上面提及的偏函数和“柯里化”函数功能,柯里化是另一个有趣的处理函数技术。柯里化(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)curry(func)
的结果是function(a)
的一个包装器。
(2)当调用sum(1)
是,参数被保存在词法环境中,然后返回新的包装器function(b)
(3)然后sum(1)(2)
提供2并最终调用function(b)
,然后传递调用给原始多参数函数sum
。
高级柯里化实现
有一些柯里化的高级实现,可以实现更复杂功能:其返回一个包装器,它允许函数提供全部参数被正常调用,或返回偏函数。实现如下:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {//如果参数大于等于函数参数,那么允许函数提供全部参数被正常调用
return func.apply(this, args);
} else {//提供参数小于函数参数,返回偏函数
return function pass(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
} function sum(a, b, c) {
return a + b + c;
} let curriedSum = curry(sum); // 提供全部参数,正常调用
alert( curriedSum(, , ) ); // 6 // 返回偏函数包装器,并提供2、3参数
alert( curriedSum()(,) ); //
当我们运行时,有两个分支:
1、提供全部参数正常调用:如果传递args
数与原函数已经定义的参数个数一样或更长,那么直接调用。
2、获得偏函数:否则,不调用func
函数,返回另一个包装器,提供连接之前的参数一起做为新参数重新应用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)
——最终有三个参数,传递给原始函数,然后参数个数相等,就直接调用func函数。
总结
1、当把已知函数的一些参数固定,结果函数被称为偏函数。通过使用bind
获得偏函数,也有其他方式实现。
用途:当我们不想一次一次重复相同的参数时,偏函数是很便捷的。如我们有send(from,to)
函数,如果from
总是相同的,可以使用偏函数简化调用。
2、柯里化是转换函数调用从f(a,b,c)
至f(a)(b)(c)
,Javascript通常既实现正常调用,也实现参数数量不足时的偏函数方式调用。
用途:(1)参数复用;(2)提前返回;(3)延迟计算或运行,参数随意设置。
这里说一下“提前返回”,很常见的一个例子:兼容现代浏览器以及IE浏览器的事件添加方法。我们正常情况可能会这样写:
var addEvent = function(el, type, fn, capture) {
if (window.addEventListener) {
el.addEventListener(type, function(e) {
fn.call(el, e);
}, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
}
};
上面的方法有什么问题呢?很显然,我们每次使用addEvent为元素添加事件的时候,(eg. IE6/IE7)都会走一遍if...else if ...其实只要一次判定就可以了,怎么做?——柯里化。改为下面这样子的代码:
var addEvent = (function(){
if (window.addEventListener) {
return function(el, sType, fn, capture) {
el.addEventListener(sType, function(e) {
fn.call(el, e);
}, (capture));
};
} else if (window.attachEvent) {
return function(el, sType, fn, capture) {
el.attachEvent("on" + sType, function(e) {
fn.call(el, e);
});
};
}
})();
初始addEvent的执行其实只实现了部分的应用(只有一次的if...else if...判定),而剩余的参数应用都是其返回函数实现的,典型的柯里化思想。
理解JS里的偏函数与柯里化的更多相关文章
- js 高阶函数之柯里化
博客地址:https://ainyi.com/74 定义 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且 ...
- [转]js函数式变成之函数柯里化
本文转自:https://segmentfault.com/a/1190000003733107 函数柯里化是指参数逐渐求值的过程. 我觉得它是:降低通用性,提高专用性. 通常,柯里化是这样的过程,“ ...
- 前端进击的巨人(五):学会函数柯里化(curry)
柯里化(Curring, 以逻辑学家Haskell Curry命名) 写在开头 柯里化理解的基础来源于我们前几篇文章构建的知识,如果还未能掌握闭包,建议回阅前文. 代码例子会用到 apply/call ...
- JavaScript函数柯里化的一些思考
1. 高阶函数的坑 在学习柯里化之前,我们首先来看下面一段代码: var f1 = function(x){ return f(x); }; f1(x); 很多同学都能看出来,这些写是非常傻的,因为函 ...
- 理解运用JS的闭包、高阶函数、柯里化
JS的闭包,是一个谈论得比较多的话题了,不过细细想来,有些人还是理不清闭包的概念定义以及相关的特性. 这里就整理一些,做个总结. 一.闭包 1. 闭包的概念 闭包与执行上下文.环境.作用域息息相关 执 ...
- js高阶函数应用—函数柯里化和反柯里化(二)
第上一篇文章中我们介绍了函数柯里化,顺带提到了偏函数,接下来我们继续话题,进入今天的主题-函数的反柯里化. 在上一篇文章中柯里化函数你可能需要去敲许多代码,理解很多代码逻辑,不过这一节我们讨论的反科里 ...
- JS 函数的柯里化与反柯里化
===================================== 函数的柯里化与反柯里化 ===================================== [这是一篇比较久之前的总 ...
- 【转载】JS中bind方法与函数柯里化
原生bind方法 不同于jQuery中的bind方法只是简单的绑定事件函数,原生js中bind()方法略复杂,该方法上在ES5中被引入,大概就是IE9+等现代浏览器都支持了(有关ES5各项特性的支持情 ...
- js高阶函数应用—函数柯里化和反柯里化
在Lambda演算(一套数理逻辑的形式系统,具体我也没深入研究过)中有个小技巧:假如一个函数只能收一个参数,那么这个函数怎么实现加法呢,因为高阶函数是可以当参数传递和返回值的,所以问题就简化为:写一个 ...
随机推荐
- efi转bios详细说明
前言 制作好的efi格式的ubuntu15.10系统放到服务器主板上启动不了,于是将其改为bios格式,发现问题解决了,成功登入系统.下面是操作过程的一个记录. 测试环境 目标环境 系统: Ubunt ...
- 解析gtest框架运行机制
前言 Google test是一款开源的白盒单元测试框架,据说目前在Google内部已在几千个项目中应用了基于该框架的白盒测试. 最近的工作是在搞一个基于gtest框架搭建的自动化白盒测试项目,该项目 ...
- Java坦克大战 (一) 之产生一个窗口
本文来自:小易博客专栏.转载请注明出处:http://blog.csdn.net/oldinaction 在此小易将坦克大战这个项目分为几个版本,以此对J2SE的知识进行回顾和总结,希望这样也能给刚学 ...
- DIV+CSS设置及问题总结
HTML 中有用的字符实体 注释:实体名称对大小写敏感! 显示结果 描述 实体名称 实体编号 空格 < 小于号 < < > 大于号 > > & ...
- mysql索引语法及示例
注:本篇文章是对菜鸟教程中的mysql索引(http://www.runoob.com/mysql/mysql-index.html)的翻译版本:添加了示例,便于理解: 索引分单列索引和组合索引.单列 ...
- 【 HAProxy 】学习笔记
一.haproxy的功能: HAProxy vs LVS HAProxy支持tcp和http两种代理模式,而lvs仅支持tcp代理模式 HAProxy相比LVS的使用要简单 ...
- 【 Linux 】I/O工作模型及Web服务器原理
一.进程.线程 进程是具有一定独立功能的,在计算机中已经运行的程序的实体.在早期系统中(如linux 2.4以前),进程是基本运作单位,在支持线程的系统中(如windows,linux2.6) ...
- 搜索引擎--范例:谈谈django--mysql数据库的一些常用命令
现在基本没有什么能离得开数据库了,django我一直用的都是mysql的数据库,这次和大家说说django--mysql数据库的一些常用命令吧 1:命令行登陆mysql C:\Users\Admini ...
- Appium+python自动化28-name定位【转载】
本篇转自博客:上海-悠悠 前言 appium1.5以下老的版本是可以通过name定位的,新版本从1.5以后都不支持name定位了 一. name定位报错 1.最新版appium V1.7用name定位 ...
- 使用Bind服务配置DNS服务器
bind是什么 bind是DNS服务器软件 ,他的服务名称是named 功能区分: 正向解析:根据主机名查找对应的IP地址 反向解析:根据IP地址查找对应的主机名(域名) 工作形式上区分: 主服务器: ...