两个不同的主题,所以我将分别解释它们:
使用函数作为方法参数
首先,更正:您提供的示例不是“一个方法返回具有命名输入参数的另一个方法”的示例。它们是一个函数作为另一个方法的输入参数的例子。
为了澄清起见,我将举一个例子,其中一个函数的返回值被用作另一个函数的输入。
var a = "Hello ",
b = "World!";
var c = a.concat( b.toUpperCase() ); //c = "Hello WORLD!"
为了创建c
,按顺序发生以下情况:
- 浏览器开始解析
concat
方法,但需要弄清楚给它什么参数。
- 该参数包含一个方法调用,因此执行的
toUpperCase()
方法b
,返回字符串“WORLD!”。
- 返回的字符串成为现在可以执行
a
的concat()
方法的参数。
就concat()
方法而言,结果和你写的一样c = a.concat("WORLD!")
——它不在乎字符串“WORLD!” 由另一个函数创建。
你可以告诉大家的返回值的b.toUpperCase()
作为参数传递,而不是函数本身,因为在函数名末尾括号中。函数名称后的括号告诉浏览器执行该函数,只要它已经计算出该函数的任何参数的值。 如果没有括号,函数将被视为任何其他对象,并且可以作为参数或变量传递,而无需实际执行任何操作。
当一个未执行的函数对象被用作另一个函数的参数时,发生的事情完全取决于第二个函数中的指令。如果您将该函数对象传递给console.log()
,则该函数的字符串表示将被打印到控制台,而不会执行您传入的函数。但是,大多数接受另一个函数作为输入的方法都被设计为使用指定的参数调用该函数。
一个例子是map()
数组的方法。该map
方法创建一个新数组,其中每个元素都是对原始数组的相应元素运行映射函数的结果。
var stringArray = ["1", "2!", "3.0", "?"];
var numberArray = stringArray.map(parseFloat); //numberArray = [1, 2, 3, NaN]
该函数parseFloat()
是一个内置函数,它接受一个字符串并试图从中找出一个数字。请注意,当我将它传递给 map 函数时,我只是将它作为变量名传递,而不是在最后用括号执行它。它由map函数执行,由map函数决定它得到什么参数。每次调用的结果parseFloat
由map
函数分配到它们在结果数组中的位置。
具体来说,map 函数parseFloat
使用三个参数执行:数组中的元素、该元素在数组中的索引以及整个数组。parseFloat
但是,该函数仅使用一个参数,因此忽略第二个和第三个参数。(parseInt
但是,如果你尝试用 做同样的事情,你会得到意想不到的结果,因为parseInt
它使用了第二个参数——它被视为整数的基数(“基数”)。)
map 函数并不关心传入函数需要多少个参数,当然也不关心在该函数中使用了哪些变量名。如果您自己编写 map 函数,它看起来像这样:
Array.prototype.myMap = function(f) {
var result = [];
for (var i = 0, n=this.length; i<n; i++) {
result[i] = f( this[i], i, this);
//call the passed-in function with three parameters
}
return result;
};
该f
函数被调用,并给定参数,无需了解它是什么或它做什么东西。参数按特定顺序给出——元素、索引、数组——但不链接到任何特定的参数名称。
现在,类似map
方法的限制是可以直接调用的 Javascript 函数非常少,只需将值作为参数传递即可。大多数函数都是特定对象的方法。例如,我们不能将该toUpperCase
方法用作 的参数map
,因为它toUpperCase
仅作为字符串对象的方法存在,并且仅作用于该特定字符串对象,而不作用于 map 函数可能给它的任何参数。为了将字符串数组映射为大写,您需要创建自己的函数,该函数的工作方式与 map 函数将使用它的方式相同。
var stringArray = ["hello", "world", "again"];
function myUpperCase(s, i, array) {
return s.toUpperCase(); //call the passed-in string's method
}
var uppercaseStrings = stringArray.map( myUpperCase );
//uppercaseStrings = ["HELLO", "WORLD", "AGAIN"]
但是,如果您只打算使用该函数myUpperCase
一次,则无需单独声明它并为其命名。您可以直接将其用作匿名函数。
var stringArray = ["hello", "world", "again"];
var uppercaseStrings = stringArray.map(
function(s,i,array) {
return s.toUpperCase();
}
);
//uppercaseStrings still = ["HELLO", "WORLD", "AGAIN"]
开始看起来很熟悉?这是许多 d3 和 JQuery 函数使用的结构——您传入一个函数,作为函数名或匿名函数,d3/JQuery 方法在选择的每个元素上调用您的函数,传入指定值作为第一个、第二个甚至第三个参数。
那么参数名称呢?正如您所提到的,它们可以是您想要的任何东西。我可以在我的函数中使用非常长的描述性参数名称:
function myUpperCase(stringElementFromArray, indexOfStringElementInArray, ArrayContainingStrings) {
return stringElementFromArray.toUpperCase();
}
传递给函数的值将是相同的,完全基于 map 函数传递它们的顺序。事实上,因为我从不使用索引参数或数组参数,我可以将它们排除在我的函数声明并只使用第一个参数。其他值仍然由 传入map
,但它们被忽略。但是,如果我想使用传入的第二个或第三个参数map
,我将不得不为第一个参数声明一个名称,以保持编号正确:
function indirectUpperCase (stringIDontUse, index, array) {
return array[index].toUpperCase();
}
这就是为什么,如果你想在 d3 中使用索引号,你必须写function(d,i){return i;}
. 如果你只是做function(i){return i;}
的值i
将是数据对象,因为这就是 d3 函数总是作为第一个参数传递的,不管你怎么称呼它。传入参数值的是外部函数。参数名称仅存在于内部函数中。
必要的警告和例外:
我说过 map 函数并不关心传入函数需要多少个参数。确实如此,但其他外部函数可以使用传入函数的.length
属性来确定预期的参数数量并相应地传递不同的参数值。
你不必有命名为函数的参数,以获得传入的参数值。您还可以使用该arguments
函数内的列表访问它们。因此,另一种编写大写映射函数的方法是:
function noParamUpperCase() {
return arguments[0].toUpperCase();
}
但是,请注意,如果外部函数使用参数的数量来确定要传递给内部函数的值,则该函数似乎不接受任何参数。
方法链
你会注意到我上面没有提到方法链。那是因为它是一个完全独立的代码模式,恰好在 d3 和 JQuery 中也经常使用。
让我们回到第一个例子,它创建了“Hello WORLD!” 来自“你好”和“世界!”。如果您想创建“HELLO World!”怎么办?反而?你可以做
var a = "Hello ",
b = "World!";
var c = a.toUpperCase().concat( b ); //c = "HELLO World!"
在创建中发生的第一件事c
是a.toUpperCase()
方法被调用。该方法返回一个字符串(“HELLO”),它像所有其他字符串一样有一个.concat()
方法。所以.concat(b)
现在作为返回字符串的方法被调用,而不是原始字符串的方法a
。结果是b
连接到大写版本的末尾a
:“HELLO World!”。
在那种情况下,返回值是一个与起始对象类型相同的新对象。在其他情况下,它可能是完全不同类型的数据。
var numberArray = [5, 15];
var stringArray = numberArray.toString().split(","); //stringArray = ["5", "15"]
我们从一个数字数组开始,[5,15]
。我们调用数组的toString()
方法,该方法生成数组的格式良好的字符串版本:“5,15”。此字符串现在具有所有可用的字符串方法,包括.split()
,它将字符串拆分为围绕指定拆分字符的子字符串数组,在本例中为逗号。
您可以将其称为一种方法链,调用另一个方法返回的值的方法。但是,当使用方法链来描述 Javascript 库的特性时,关键在于方法的返回值是调用方法的同一对象。
所以当你做
d3.select("body").style("background", "green");
该d3.select("body")
方法创建一个 d3 选择对象。该对象有一个style()
方法。如果您使用 style 方法设置样式,则您实际上不需要从中获取任何信息。它本可以设计为根本不返回任何值。相反,它返回该方法所属的this
对象(对象)。因此,您现在可以调用该对象的另一个方法。或者你可以将它分配给一个变量。或两者。
var body = d3.select("body").style("background", "green")
.style("max-width", "20em");
但是,您必须始终注意不返回相同对象的方法。例如,在很多 d3 代码示例中你看到
var svg = d3.select("svg").attr("height", "200")
.attr("width", "200")
.append("g")
.attr("transform", "translate(20,20)");
现在,该方法append("g")
不会返回相同的<svg>
元素选择。它返回一个由元素组成的新选择<g>
,然后给它一个转换属性。分配给变量svg
的值是链中的最后一个返回值。因此,在后面的代码中,您必须记住,变量svg
实际上并不是指<svg>
元素的选择,而是指<g>
. 我觉得这很令人困惑,所以我尽量避免将变量名称svg
用于实际上不是<svg>
元素的选择。
当然,这些 d3.attr()
或.style()
方法中的任何一个都可以将函数作为第二个参数而不是字符串。但这不会改变方法链的工作方式。