-
Notifications
You must be signed in to change notification settings - Fork 2
JavaScript中的不完全函数调用
除非你使用另一种函数式编程语言,比如ML或者Haskell,否则,你可能对不完全调用函数调用和柯里化函数并不熟悉。但是一旦你理解了这些概念,你就可以在自己的代码中使用它们了。
注:这篇文章之前发布在MSDN上,但是已经被大幅重写了。这个最新版本更加准确。
函数
如果你是新手,即使你已经明白了js方法如何返回方法,并且也了解了传递方法作为函数参数,我还是建议你阅读第一章节。如果你已经掌握了这些知识,那可以跳过这个章节直接去阅读不完全函数调用章节
现在让我们潜下心来看个非常简单的例子:
function add(a, b) {
return a + b;
}
add(1, 2); // 3
add(1, 3); // 4
add(1, 10); // 11
add(1, 9000); // 9001
这个例子很简单,设想一下,你必须重复的调用一个函数,传递的第一个参数又是一样的。因为不必要的重复传递是导致错误的主要因素,所以一个方法就是你把重复的常量提取出来放到一个变量中,然后再在进行调用。
function add(a, b) {
return a + b;
}
var value = 1;
add(value, 2); // 3
add(value, 3); // 4
add(value, 10); // 11
add(value, 9000); // 9001
如你所见,使用变量替换使得代码具有更强的可修改性,当然也就更加可维护了。但是,这个代码还是有一些不必要的重复。如果新增一个固定的取值能够被执行无数次,那将需要我们去创建一个具有内置行为的专门函数。
函数调用函数
无论你是自己写代码,还是提供API,如果创建一个针对通用功能的专有包装函数,将会非常有用的。它可以被传递给到函数中并被执行。
一个方法就是准对通用功能,手工定义一个更加专有的函数。这在javascript中非常容易,因为函数可以调用其他函数
// More general function.
function add(a, b) {
return a + b;
}
add(1, 2); // 3
add(10, 3); // 13
// More specific functions.
function addOne(b) {
return add(1, b);
}
addOne(2); // 3
addOne(3); // 4
function addTen(b) {
return add(10, b);
}
addTen(2); // 12
addTen(3); // 13
通过这种方式定义的专有函数非常容易和直观,如果你拥有太多这样的函数,那就需要维护一些额外的代码了。
函数返回函数
这里你可以看到函数makeAdder,当被以传递一个变量调用的时候,它返回了一个新的函数(函数生成其他函数或者对象一般被认为是工厂)。返回的函数会加上传递的参数到之前制定的函数参数值,得到最终的结果。
// More general function.
function add(a, b) {
return a + b;
}
add(1, 2); // 3
add(10, 3); // 13
// More specific function generator.
function makeAdder(a) {
return function(b) {
return a + b;
};
}
// More specific functions.
var addOne = makeAdder(1);
addOne(2); // 3
addOne(3); // 4
var addTen = makeAdder(10);
addTen(2); // 12
addTen(3); // 13
在JS中可以这么做,因为JS支持闭包。闭包允许函数访问外层变量,甚至是当前运行环境之外的变量。 同时在JS中,函数是一类公民(译者注:first-class是指函数能够被当成参数进行传递到函数中),正因为如此,函数可以接受函数作为参数,也能够返回函数。闭包和函数经常一起配合,返回函数作为参数继续进行运算。
这个例子提供了一个方便的渠道,使你直接调用addOne(2)而不是add(1, 2),但是这个不是没有代价的。首先,通用增加函数和makeAdder 工厂的实际加逻辑是重复的。这样的代码就不够DRY了。第二,通过这种方式处理的不同函数,你需要手工创建一个工厂。
函数接收函数
Functions Accepting Functions
下一步就是创建一个更加通用的工厂函数。这个工厂函数不仅仅可以接收一个参数,而且还可以接收一个函数,并且能够运行改函数。
(函数当成参数传递给其他函数一般指回调)。
通过这种方式,你能够创建一个简单的工厂函数去创建一个新的函数。
注意:原始函数不会被修改,他们行为不会变。他们可以可以被wrapper函数轻松的引用到并且被调用。
// Relatively flexible, more specific function generator.
function bindFirstArg(fn, a) {
return function(b) {
return fn(a, b);
};
}
// More general functions.
function add(a, b) {
return a + b;
}
add(1, 2); // 3
function multiply(a, b) {
return a * b;
}
multiply(10, 2); // 20
// More specific functions.
var addOne = bindFirstArg(add, 1);
addOne(2); // 3
addOne(3); // 4
addOne(10); // 11
addOne(9000); // 9001
var multiplyByTen = bindFirstArg(multiply, 10);
multiplyByTen(2); // 20
multiplyByTen(3); // 30
multiplyByTen(10); // 100
multiplyByTen(9000); // 90000
Where this is especially interesting is that not only can the bindFirstArg function be used to bind any first argument to any arbitrary function, it can be used to bind a function to itself as its own first argument, thus creating a “bindable” function.
Think about it this way: If the bindFirstArg function can bind the first argument to a function that takes two arguments—like 1 to add or 10 to multiply—and the bindFirstArg function itself takes two arguments, it stands to reason that bindFirstArg can bind a function as the first argument to itself.
Here, a more specific version of bindFirstArg is created with its first argument, the add function, bound to itself.
// More specific function generator. var makeAdder = bindFirstArg(bindFirstArg, add); // More specific functions. var addOne = makeAdder(1); addOne(2); // 3 addOne(3); // 4 var addTen = makeAdder(10); addTen(2); // 12 addTen(3); // 13Does this look familiar? It should. This is the makeAdder function, just created using a significantly more generalized approach than before.
Now, while the bindFirstArg function is more flexible than the previous examples, it’s only slightly more flexible. What if you want to be able to bind more than just that first argument? What if you have a function that accepts three or more arguments and you want to bind either the first argument or both of the first two arguments or any number of arguments, depending on the circumstance?
Even though this solution is more flexible than before, it can be generalized.