标签:
局部套用或局部应用是一个会让那些熟悉传统javascript代码编写方式的人听起来感到困惑的函数式技术,但是如果应用得当的话,它确实可以让你的javascript代码更具有可读性。
更强的可读性,更强的灵活性
函数式javascript代码的一个优势在于它的代码更加简短,严格,它可以用最少行数和更少重复性的代码说到点子上,当然,有时候这会牺牲可读性。而在你熟悉这种函数式编程的工作方式之前,以这种方式编写的代码会让你更难阅读、更难理解!
如果你以前遇到过局部套用这一术语,但是从来不知道这是什么意思,那么你是可以被原谅的,因为他们认为这是一些你不需要烦恼的奇怪的技术。但是局部套用实际上是一个非常简单的概念,它解决了我们在处理函数参数时的一些熟悉的问题,并且为开发者开放了一系列灵活的选项。
什么是局部套用?
简而言之,局部套用是构造函数允许你在其中局部应用函数参数的一种方式,这意味着你可以传递函数所期望的所有参数并且得到结果,或者传递这个参数的子集并且返回一个等待剩余参数的函数。真的就是这么简单。
局部套用是一种类似Haskell和Scala这样围绕函数式概念的语言元素,javascript有函数式功能,但局部套用不是默认的创建方式(至少在当前版本的语言),但是我们依旧知道了一些函数式的技巧,并且我们也可以在javascript中让局部套用为我们工作。
为了理解局部套用是如何工作的,让我们在javascript中使用熟悉的语法来创建第一个我们想要的具有局部套用功能的函数。举个例子:让我们来想象一个通过名字来问候别人的函数,我们都知道怎么创建一个简单的带有名字和问候方式的问候函数,并且在控制台中输出带有名字的问候的日志:
var greet = function(greeting,name){ console.log(greeting+","+name); }; greet("Hello","Heidi"); // "Hello,Heidi"
这个函数需要 name 和 greeting 作为参数传递进来,以便能正常工作,但是我们可以通过使用简单的嵌套的局部套用来重写这个函数,这样的话基础函数就只需要 greeting 一个参数,并且该函数返回另外一个带有我们想要问候的人的名字的函数。
我们的第一个局部套用
var greetCurried = function(greeting){ return function(name){ console.log(greeting+","+name); }; };
我们通过这个微小的调整所写的函数让我们创建了一个可以有任何类型的问候方式的新函数,并且将我们想要问候的人的名字传递给这个新函数:
var greetHello = greetCurried("Hello"); greetHello("Heidi"); // "Hello,Heidi" greetHello("Eddie"); // "Hello,Eddie"
我们也可以直接调用原始的局部套用的函数,只需要在每一组单独的括号中传递参数,保证每个参数的正确性:
greetCurried("Hi there")("Howard"); // "Hi there,Howard"
为什么不在你的浏览器中试试呢?
这是javascript代码:
这是浏览器控制台中的效果:
局部套用所有的东西
非常酷的是,既然我们已经学会了如何使用这种方法来处理参数从而修改我们的传统函数,我们同样可以使用这种方法来处理更多我们想要的参数:
var greetDeeplyCurried = function(greeting){ return function(separator){ return function(emphasis){ return function(name){ console.log(greeting+separator+name+emphasis); }; }; }; };
我们的函数有四个参数和有两个参数具有同样的灵活性,不管嵌套有多深,我们都可以创建新的自定义的函数来尽可能多的问候那些我们挑选出来的适合我们目的的人。
var greetAwkwardly = greetDeeplyCurried("Hello")("...")("?"); greetAwkwardly("Heidi"); // "Hello...Heidi?" greetAwkwardly("Eddie"); // "Hello...Eddie?"
更重要的是,当我们在原始的局部套用函数里创建自定义的变量的时候,我们可以传递尽可能多的参数,(通过)创建一个可以获取适当的附加参数的数量,每一个被传进来的参数都在它自己的括号(即作用范围)中:
var sayHello = greetDeeplyCurried("Hello")(", "); sayHello(".")("Heidi"); // "Hello, Heidi." sayHello(".")("Eddie"); // "Hello, Eddie."
并且我们可以很容易的定义从属变量:
var askHello = sayHello("?"); askHello("Heidi"); // "Hello, Heidi?" askHello("Eddie"); // "Hello, Eddie?"
这是javascript代码:
这是浏览器中的效果:
局部套用传统函数
你可以看到这种方法是多么强大,尤其是如果你需要创建很多非常详细的自定义函数。唯一的问题是语法。当你创建了局部套用函数时,你需要让嵌套函数返回,并且通过需要多组括号的新函数来调用他们,每一个括号都包含自己独立的参数,这会让人感到混乱。
为了解决这个问题,一种方法是创建一个快速的、肮脏(肮脏主要是创建了全局变量,污染了全局环境)的可以获取到一个没有任何嵌套返回的已存在的函数名称的局部套用函数(结合下列代码说明即:创建一个全局的局部套用函数,名字为curryIt,获取uncurried作为参数,uncurried是一个已存在的函数,无任何嵌套返回)。一个局部套用函数需要取出它的参数列表,并且使用取出的参数列表来返回初始函数的一个局部套用的版本:
var curryIt = function(uncurried){ var parameters = Array.prototype.slice.call(arguments,1); return function(){ return uncurried.apply(this,parameters.concat(Array.prototype.slice.call(arguments,0))); }; };
使用这个,我们传递一个带有任意数量参数(正如我们预先想要的那么多的数量)的函数名字,我们得到的是一个等待剩余参数的函数:
var greeter = function(greeting, separator, emphasis, name) { console.log(greeting + separator + name + emphasis); }; var greetHello = curryIt(greeter, "Hello", ", ", "."); greetHello("Heidi"); //"Hello, Heidi." greetHello("Eddie"); //"Hello, Eddie."
就像以前一样,当我们通过原始的局部套用函数来创建派生函数的,我们并没有限制我们想要的参数的数量:
var greetGoodbye = curryIt(greeter, "Goodbye", ", "); greetGoodbye(".", "Joe"); //"Goodbye, Joe."
这是javascript代码:
这是浏览器中的效果:
开始认真考虑局部套用
我们所举的几个小的局部套用函数可能无法处理所用的边界情况,例如参数的丢失或者可选,但是只要我们在传递参数时保持严格的语法,那么它确实是一个合理的工作方式。
一些函数式javascript库(例如Ramda)拥有更灵活的可以不限制所需参数的局部套用函数,并且允许你单独或者整体的来传递参数,从而创建自定义的局部套用的变量(即:
var curryIt = function(uncurried){....};
)。如果你想要使用拓展局部套用,那么这可能是一种不错方式。
不管你如何选择添加局部套用到你的编程中,选择使用嵌套括号或者你更倾向包含一个更健壮的承载函数,为你的局部套用函数想出一个一致的命名规则会让你的代码更具有可读性。函数的每一个派生变量(即超出函数给出参数的个数的变量)都应该有一个清晰的名字,这个名字能说明它的行为和预期想要的参数性质。
参数顺序
一件很重要的事就是你要记住局部套用的参数的顺序,使用我们所描述的方法,你显然需要一个能逐个进行替换(即顺序替换)的参数。
事先考虑参数的顺序会让你更容易的为局部套用定计划,并且将它应用到你的工作中。并且当你在设计函数时考虑在你的顺序参数列表中哪些参数改变次数可能最少将会是一个好的习惯。
结论
局部套用在函数式javascript中是一个非常有用的技术。它允许你生成一个很小的库,轻松配置表现一致的函数,能快速使用,并且当别人阅读你的代码的时候可以理解。增加局部套用到你的编程实践中将会促使你在代码中使用部分应用功能,避免了潜在的重复性,并且可以让你在写命名和处理函数参数的时候有更好的习惯。
标签:
原文地址:http://my.oschina.net/meichao/blog/521206