Currying(柯里化)柯里化(Currying)[1]是一门关于函数的高级技术。它不仅用于JavaScript,还用于其他编程语言。柯里化是函数的一种转换,指的是将函数从可调用的f(a,b,c)转换为可调用的f(a)(b)(c)。柯里化不调用函数。它只是转换功能。让我们看一个示例以更好地理解我们在说什么,然后继续进行实际应用。我们将创建一个辅助函数curry(f),它将对两个参数的函数f执行柯里化。换句话说,对一个有两个参数的函数f(a,b)执行curry(f)会将它变成一个以f(a)(b)运行的函数:functioncurry(f){//curry(f)Executecurried转换returnfunction(a){returnfunction(b){returnf(a,b);};};}//使用函数sum(a,b){returna+b;}letcurriedSum=curry(sum);alert(curriedSum(1)(2));//3如你所见,实现非常简单:只有两个包装器。curry(func)的结果是一个包装函数(a)。当它像curriedSum(1)一样被调用时,它的参数被保存在词法环境中,并返回一个新的包装器function(b)。然后使用2作为参数调用此包装器,并将该调用传递给原始sum函数。柯里化的更高级实现,例如lodash库的_.curry[2],返回一个允许正常或部分调用函数的包装器:functionsum(a,b){returna+b;}letcurriedSum=_.curry(sum);//使用lodash库中的_.curryalert(curriedSum(1,2));//3,仍然可以调用alert(curriedSum(1)(2));//3,部分调用柯里化功能?什么目的?要了解它的好处,我们需要一个实际的例子。例如,我们有一个日志函数log(date,importance,message)用于格式化和输出消息。在实际项目中,此类函数有很多有用的功能,比如通过网络发送日志(log),这里我们只使用alert:functionlog(date,importance,message){alert(`[${date.getHours()}:${date.getMinutes()}][${importance}]${message}`);}让我们咖喱吧!日志=_.咖喱(日志);柯里化后日志还是正常运行:log(newDate(),"DEBUG","somedebug");//log(a,b,c)...但也可以柯里化的形式运行:log(newDate())("DEBUG")("somedebug");//log(a)(b)(c)现在我们可以轻松地为当前日志创建便利函数://logNow将是日志的部分函数固定的第一个参数letlogNow=log(newDate());//使用它logNow("INFO","message");//[HH:mm]INFOmessage现在,logNow是带有固定第一个参数的日志,即更短的“的部分应用功能”或“部分功能”。我们可以更进一步,为当前调试日志提供一个方便的函数:letdebugNow=logNow("DEBUG");debugNow("message");//[HH:mm]DEBUGmessage所以:柯里化之后,我们什么都没有丢失:log仍然可以正常调用。我们可以很容易地生成偏函数,比如今天用来生成日志的偏函数。高级柯里化实现如果您想了解更多详细信息,这里是多参数函数柯里化的“高级”实现,我们也可以将其用于上面的示例。它很短:}else{returnfunction(...args2){returncurried.apply(this,args.concat(args2));}}};}用例:functionsum(a,b,c){returna+b+c;}letcurriedSum=curry(sum);alert(curriedSum(1,2,3));//6,仍然可以正常调用alert(curriedSum(1)(2,3));//6,curriedalertfor第一个参数(curriedSum(1)(2)(3));//6、fullcurrynewcurry可能看起来有点复杂,但是很容易理解。curry(func)调用的结果是一个包装器curried,如下所示://funcisthefunctiontoconvertfunctioncurried(...args){if(args.length>=func.length){//(1)returnfunc.apply(this,args);}else{returnfunctionpass(...args2){//(2)returncurried.apply(this,args.concat(args2));}}};当我们运行它时,这里有两个if执行分支:立即调用:如果传入的args的长度等于或大于原始函数(func.length)定义的长度,则将调用传递给它。获取部分函数:否则,func尚未被调用。相反,返回另一个包装器传递,它将重新应用柯里化,将先前传递的参数与新参数一起传递。然后,在新的调用中,我们将再次获得一个新的部分函数(如果没有足够的参数)或最终结果。例如,让我们看一下示例sum(a,b,c)。它有三个参数,所以sum.length=3。对于对curried(1)(2)(3)的调用:对curried(1)的第一次调用在LexicalEnvironment中保存1并返回包装器传递。wrapperpass使用参数(2)调用:它采用前一个参数(1),将其与获得的(2)连接起来,并一起调用curried(1,2)。由于参数个数还不到3个,curry函数还是会返回pass。使用参数(3)再次调用包装器传递。在接下来的调用中,pass(3)会得到之前的参数(1,2)并将3与它结合起来,执行调用curried(1,2,3)——最后有3个参数,传入最原始功能。如果这还不够清楚,您可以在头脑中或纸上复习一下函数调用的顺序。仅允许具有固定参数长度的函数Currying要求函数具有固定数量的参数。采用剩余参数的函数,例如f(...args),不能以这种方式柯里化。不仅仅是柯里化根据定义,柯里化应该将sum(a,b,c)转换为sum(a)(b)(c)。然而,如前所述,大多数JavaScript中的柯里化实现都是高级的:它们允许使用多参数变体调用函数。总结Currying是一种将f(a,b,c)转换为可以称为f(a)(b)(c)的东西的转换。JavaScript实现通常保持函数可正常调用,如果参数个数不足则返回部分函数。柯里化让我们更容易获得偏函数。正如我们在日志记录示例中看到的,普通函数log(date,importance,message)是柯里化的,当我们调用它时我们传递一个参数(例如log(date))或两个When参数(log(date,importance)),它返回偏函数。现代JavaScript教程:关于开源现代JavaScript的高级教程的优秀介绍。React官方文档推荐的JavaScript学习教程和MDN[3]。免费在线阅读:https://zh.javascript.info参考资料[1]Currying(柯里化):https://en.wikipedia.org/wiki/Currying[2]_.curry:https://lodash.com/docs#curry[3]React官方文档推荐,与MDN并行的JavaScript学习教程:https://zh-hans.reactjs.org/docs/getting-started.html#javascript-resources本文转载自微信公众号“技术讲座”,您可以通过以下二维码关注。转载本文请联系TechnicalTalk公众号。
