js函数柯里化
栏目: JavaScript · 发布时间: 5年前
内容简介:由于最近离职找工作的事情,好久不写文章了,就只好把2017年写的文章搬出来骗骗赞了hhh。用我自己的话来总结一下,函数柯里化的意思就是你可以一次传很多参数给curry函数,也可以分多次传递,curry函数每次都会返回一个函数去处理剩下的参数,一直到返回最后的结果。这里还是举几个例子来说明一下:
由于最近离职找工作的事情,好久不写文章了,就只好把2017年写的文章搬出来骗骗赞了hhh。
概念
用我自己的话来总结一下,函数柯里化的意思就是你可以一次传很多参数给curry函数,也可以分多次传递,curry函数每次都会返回一个函数去处理剩下的参数,一直到返回最后的结果。
实例
这里还是举几个例子来说明一下:
柯里化求和函数
// 普通方式 var add1 = function(a, b, c){ return a + b + c; } // 柯里化 var add2 = function(a) { return function(b) { return function(c) { return a + b + c; } } } 复制代码
这里每次传入参数都会返回一个新的函数,这样一直执行到最后一次返回a+b+c的值。 但是这种实现还是有问题的,这里只有三个参数,如果哪天产品经理告诉我们需要改成100次?我们就重新写100次?这很明显不符合开闭原则,所以我们需要对函数进行一次修改。
var add = function() { var _args = []; return function() { if(arguments.length === 0) { return _args.reduce(function(a, b) { return a + b; }) } [].push.apply(_args, arguments); return arguments.callee; } } var sum = add(); sum(100, 200)(300); sum(400); sum(); // 1000 复制代码
我们通过判断下一次是否传进来参数来决定函数是否运行,如果继续传进了参数,那我们继续把参数都保存起来,等运行的时候全部一次性运行,这样我们就初步完成了一个柯里化的函数。
通用柯里化函数
这里只是一个求和的函数,如果换成求乘积呢?我们是不是又需要重新写一遍?仔细观察一下我们的add函数,如果我们将if里面的代码换成一个函数执行代码,是不是就可以变成一个通用函数了?
var curry = function(fn) { var _args = []; return function() { if(arguments.length === 0) { return fn.apply(fn, _args); } [].push.apply(_args, arguments); return arguments.callee; } } var multi = function() { return [].reduce.call(arguments, function(a, b) { return a + b; }) } var add = curry(multi); add(100, 200, 300)(400); add(1000); add(); // 2000 复制代码
在之前的方法上面,我们进行了扩展,这样我们就已经实现了一个比较通用的柯里化函数了。 也许你想问,我不想每次都使用那个丑陋的括号结尾怎么办?
var curry = function(fn) { var len = fn.length, args = []; return function() { Array.prototype.push.apply(args, arguments) var argsLen = args.length; if(argsLen < len) { return arguments.callee; } return fn.apply(fn, args); } } var add = function(a, b, c) { return a + b + c; } var adder = curry(add) adder(1)(2)(3) 复制代码
这里根据函数fn的参数数量进行判断,直到传入的数量等于fn函数需要的参数数量才会返回fn函数的最终运行结果,和上面那种方法原理其实是一样的,但是这两种方式都太依赖参数数量了。 我在简书还看到别人的另一种递归实现方法,其实实现思路和我的差不多吧。
// 简单实现,参数只能从右到左传递 function createCurry(func, args) { var arity = func.length; var args = args || []; return function() { var _args = [].slice.call(arguments); [].push.apply(_args, args); // 如果参数个数小于最初的func.length,则递归调用,继续收集参数 if (_args.length < arity) { return createCurry.call(this, func, _args); } // 参数收集完毕,则执行func return func.apply(this, _args); } } 复制代码
这里是对参数个数进行了计算,如果需要无限参数怎么办?比如下面这种场景。
add(1)(2)(3)(2); add(1, 2, 3, 4, 5); 复制代码
这里主要有一个知识点,那就是函数的隐式转换,涉及到toString和valueOf两个方法,如果直接对函数进行计算,那么会先把函数转换为字符串,之后再参与到计算中,利用这两个方法我们可以对函数进行修改。
var num = function() { } num.toString = num.valueOf = function() { return 10; } var anonymousNum = (function() { // 10 return num; }()) 复制代码
经过修改,我们的函数最终版是这样的。
var curry = function(fn) { var func = function() { var _args = [].slice.call(arguments, 0); var func1 = function() { [].push.apply(_args, arguments) return func1; } func1.toString = func1.valueOf = function() { return fn.apply(fn, _args); } return func1; } return func; } var add = function() { return [].reduce.call(arguments, function(a, b) { return a + b; }) } var adder = curry(add) adder(1)(2)(3) 复制代码
那么我们说了那么多,柯里化究竟有什么用呢?
预加载
在很多场景下,我们需要的函数参数很可能有一部分一样,这个时候再重复写就比较浪费了,我们提前加载好一部分参数,再传入剩下的参数,这里主要是利用了闭包的特性,通过闭包可以保持着原有的作用域。
var match = curry(function(what, str) { return str.match(what); }); match(/\s+/g, "hello world"); // [ ' ' ] match(/\s+/g)("hello world"); // [ ' ' ] var hasSpaces = match(/\s+/g); // function(x) { return x.match(/\s+/g) } hasSpaces("hello world"); // [ ' ' ] hasSpaces("spaceless"); // null 复制代码
上面例子中,使用hasSpaces函数来保存正则表达式规则,这样可以有效的实现参数的复用。
动态创建函数
这个其实也是一种惰性函数的思想,我们可以提前执行判断条件,通过闭包将其保存在有效的作用域中,来看一种我们平时写代码常见的场景。
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的时候都会重新进行if语句进行判断,但是实际上浏览器的条件不可能会变化,你判断一次和判断N次结果都是一样的,所以这个可以将判断条件提前加载。
var addEventHandler = 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); }); }; } } var addEvent = addEventHandler(); addEvent(document.body, "click", function() {}, false); addEvent(document.getElementById("test"), "click", function() {}, false); 复制代码
但是这样做还是有一种缺点,因为我们无法判断程序中是否使用了这个方法,但是依然不得不在文件顶部定义一下addEvent,这样其实浪费了资源,这里有一种更好的解决方法。
var addEvent = function(el, sType, fn, capture){ if (window.addEventListener) { addEvent = function(el, sType, fn, capture) { el.addEventListener(sType, function(e) { fn.call(el, e); }, (capture)); }; } else if (window.attachEvent) { addEvent = function(el, sType, fn, capture) { el.attachEvent("on" + sType, function(e) { fn.call(el, e); }); }; } } 复制代码
在addEvent函数里面对其重新赋值,这样既解决了每次运行都要判断的问题,又解决了必须在作用域顶部执行一次造成浪费的问题。
React
在回家的路上我一直在想函数柯里化是不是可以扩展到更多场景,我想把函数换成react组件试试?我想到了高阶组件和redux的connect,这两个确实是将柯里化思想用到react里面的体现。我们想一想,如果把上面例子里面的函数换成组件,参数换成高阶函数呢?
var curry = function(fn) { var func = function() { var _args = [].slice.call(arguments, 0); var func1 = function() { [].push.apply(_args, arguments) return func1; } func1.toString = func1.valueOf = function() { return fn.apply(fn, _args); } return func1; } return func; } var hoc = function(WrappedComponent) { return function() { var len = arguments.length; var NewComponent = WrappedComponent; for (var i = 0; i < len; i++) { NewComponent = arguments[i](NewComponent) } return NewComponent; } } var MyComponent = hoc(PageList); curry(MyComponent)(addStyle)(addLoading) 复制代码
这个例子是对原来的PageList组件进行了扩展,给PageList加了样式和loading的功能,如果想加其他功能,可以继续在上面扩展(注意addStyle和addLoading都是高阶组件),但是写法真的很糟糕,一点都不coooooool,我们可以使用compose方法,underscore和loadsh这些库中已经提供了。
var enhance = compose(addLoading, addStyle); enhance(MyComponent) 复制代码
总结
其实关于柯里化的运用核心还是对函数闭包的灵活运用,深刻理解闭包和作用域后就可以写出很多灵活巧妙的方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Python 拓展之特殊函数(lambda 函数,map 函数,filter 函数,reduce 函数)
- Python 函数调用&定义函数&函数参数
- python基础教程:函数,函数,函数,重要的事说三遍
- C++函数中那些不可以被声明为虚函数的函数
- 017.Python函数匿名函数
- 纯函数:函数式编程入门
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
像计算机科学家一样思考Python (第2版)
[美] 艾伦 B. 唐尼 / 赵普明 / 人民邮电出版社 / 2016-7 / 49.00
本书以培养读者以计算机科学家一样的思维方式来理解Python语言编程。贯穿全书的主体是如何思考、设计、开发的方法,而具体的编程语言,只是提供了一个具体场景方便介绍的媒介。 全书共21章,详细介绍Python语言编程的方方面面。本书从基本的编程概念开始讲起,包括语言的语法和语义,而且每个编程概念都有清晰的定义,引领读者循序渐进地学习变量、表达式、语句、函数和数据结构。书中还探讨了如何处理文件和......一起来看看 《像计算机科学家一样思考Python (第2版)》 这本书的介绍吧!