js高级函数技巧-函数柯里化

来源:http://www.chinese-glasses.com 作者:Web前端 人气:63 发布时间:2020-03-31
摘要:时间: 2019-10-19阅读: 74标签: 柯里化引语 时间: 2019-02-24阅读: 525标签: 柯里化 最近在社区阅读技术博客的时候偶然间看到了 函数柯里化 几个字,还有要求手写 js函数柯里化 ,心想是柯里

时间: 2019-10-19阅读: 74标签: 柯里化引语

时间: 2019-02-24阅读: 525标签: 柯里化

最近在社区阅读技术博客的时候偶然间看到了函数柯里化几个字,还有要求手写js函数柯里化,心想是柯里化是什么高级的东西?没听说过啊?

我们经常说在Javascript语言中,函数是“一等公民”,它们本质上是十分简单和过程化的。可以利用函数,进行一些简单的数据处理,return结果,或者有一些额外的功能,需要通过使用闭包来实现,最后经常会return匿名函数。

就带着问题出发,专门去学习了一下,做了一些整理。

如果你对函数式编程有一定了解,函数柯里化(function currying)是不可或缺的,利用函数柯里化,可以在开发中非常优雅的处理复杂逻辑。

什么是函数柯里化?

函数柯里化

什么是函数柯里化?先看看维基百科如何解释:

柯里化(Currying),维基百科上的解释是,把接受多个参数的函数转换成接受一个单一参数的函数先看一个简单例子

在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

 // 柯里化 var foo = function(x) { return function(y) { return x + y } } foo(3)(4) // 7 // 普通方法 var add = function(x, y) { return x + y; } add(3, 4) //7 

这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的,尽管它是Moses Schönfinkel和戈特洛布·弗雷格发明的。

本来应该一次传入两个参数的add函数,柯里化方法,变成每次调用都只用传入一个参数,调用两次后,得到最后的结果。

在直觉上,柯里化声称“如果你固定某些参数,你将得到接受余下参数的一个函数”。所以对于有两个变量的函y^x,如果固定了y=2,则得到有一个变量的函数2^x。

再看看,一道经典的面试题。

Currying的概念其实并不复杂,用通俗易懂的话说:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

编写一个sum函数,实现如下功能: console.log(sum(1)(2)(3)) // 6.

如果文字解释还是有一点抽象,我们就拿add函数,来做一个简单的函数柯里化的实现。

直接套用上面柯里化函数,多加一层return

// 普通的add函数function add(x, y) { return x + y}// add函数柯里化后var curryingAdd = function(x) { return function(y) { return x + y; };};// 函数复用var increment = curryingAdd(1);var addTen = curryingAdd(10);increment(2);// 3addTen(2);// 12
 function sum(a) { return function(b) { return function(c) { return a + b + c; } } }

实际上就是把add函数的x,y两个参数变成了先用一个函数接收x然后返回一个函数去处理y参数。现在思路应该就比较清晰了,就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

当然,柯里化不是为了解决面试题,它是应函数式编程而生

为什么要函数柯里化?

如何实现

看完上面的关于add函数的柯里化,问题来了,费这么大劲封装一层,到底有什么用处呢?

还是看看上面的经典面试题。如果想实现sum(1)(2)(3)(4)(5)...(n)就得嵌套n-1个匿名函数,

一、参数复用

 function sum(a) { return function(b) { ... return function(n) { } } } 

其实刚刚第一个add函数的柯里化例子中已经涉及到了函数柯里化所带来的函数复用的便捷,我们通过add函数柯里化,很快捷地实现了increment函数和addTen函数,再来看个例子:

看起来并不优雅,如果我们预先知道有多少个参数要传入,可以利用递归方法解决

// 正常正则验证字符串 reg.test(txt)// 函数封装后function check(reg, txt) { return reg.test(txt)}check(/d+/g, 'test') //falsecheck(/[a-z]+/g, 'test') //true// Currying后function curryingCheck(reg) { return function(txt) { return reg.test(txt) }}var hasNumber = curryingCheck(/d+/g)var hasLetter = curryingCheck(/[a-z]+/g)hasNumber('test1') // truehasNumber('testtest') // falsehasLetter('21212') // false
 var add = function(num1, num2) { return num1 + num2; } // 假设 sum 函数调用时,传入参数都是标准的数字 function curry(add, n) { var count = 0, arr = []; return function reply(arg) { arr.push(arg); if ( ++count = n) { //这里也可以在外面定义变量,保存每次计算后结果 return arr.reduce(function(p, c) { return p = add(p, c); }, 0) } else { return reply; } } } var sum = curry(add, 4); sum(4)(3)(2)(1) // 10

上面的示例是一个正则的校验,正常来说直接调用check函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便。

如果调用次数多于约定数量,sum就会报错,我们就可以设计成类似这样

二、提前确认

sum(1)(2)(3)(4)(); // 最后传入空参数,标识调用结束,
var on = function(element, event, handler) { if (document.addEventListener) { if (element  event  handler) { element.addEventListener(event, handler, false); } } else { if (element  event  handler) { element.attachEvent('on' + event, handler); } }}var on = (function() { if (document.addEventListener) { return function(element, event, handler) { if (element  event  handler) { element.addEventListener(event, handler, false); } }; } else { return function(element, event, handler) { if (element  event  handler) { element.attachEvent('on' + event, handler); } }; }})();

只需要简单修改下curry函数

换一种写法可能比较好理解一点,上面就是把isSupport这个参数给先确定下来了

function curry(add) { var arr = []; return function reply() { var arg = Array.prototype.slice.call(arguments); arr = arr.concat(arg); if (arg.length === 0) { // 递归结束条件,修改为 传入空参数 return arr.reduce(function(p, c) { return p = add(p, c); }, 0) } else { return reply; } } } console.log(sum(4)(3)(2)(1)(5)()) // 15
var on = function(isSupport, element, event, handler) { isSupport = isSupport || document.addEventListener; if (isSupport) { return element.addEventListener(event, handler, false); } else { return element.attachEvent('on' + event, handler); }}

简洁版实现

我们在做项目的过程中,封装一些dom操作可以说再常见不过,上面第一种写法也是比较常见,但是我们看看第二种写法,它相对一第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断

上面针对具体问题,引入柯里化方法解答,回到如何实现创建柯里化函数的通用方法。同样先看简单版本的方法,以add方法为例,代码来自《JavaScript高级程序设计》

三、延迟计算/运行

 function curry(fn) { var args = Array.prototype.slice.call(arguments, 1); return function() { var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); return fn.apply(null, finalArgs); };}function add(num1, num2) { return num1 + num2;}var curriedAdd = curry(add, 5);var curriedAdd2 = curry(add, 5, 12);alert(curriedAdd(3)) // 8alert(curriedAdd2()) // 17
Function.prototype.bind = function (context) { var _this = this var args = Array.prototype.slice.call(arguments, 1) return function() { return _this.apply(context, args) }}

加强版实现

像我们js中经常使用的bind,实现的机制就是Currying.

上面add函数,可以换成任何其他函数,经过curry函数处理,都可以转成柯里化函数。这里在调用curry初始化时,就传入了一个参数,而且返回的函数curriedAdd,curriedAdd2也没有被柯里化。要想实现更加通用的方法,在柯里化函数真正调用时,再传参数,

本文由10bet发布于Web前端,转载请注明出处:js高级函数技巧-函数柯里化

关键词:

最火资讯