两个不同的主题,所以我将分别解释它们:
使用函数作为方法参数
首先,更正一下:您提供的示例不是“一个方法返回到另一个具有命名输入参数的方法”的示例。它们是一个函数作为另一个方法的输入参数的例子。
为了澄清,我将举一个例子,其中一个函数的返回值用作另一个函数的输入。
var a = "Hello ",
b = "World!";
var c = a.concat( b.toUpperCase() ); //c = "Hello WORLD!"
为了创建c,按顺序发生以下情况:
- 浏览器开始解析
concat方法,但需要弄清楚要给它什么参数。
- 该参数包含一个方法调用,因此执行
b的toUpperCase()方法,返回字符串“WORLD!”。
- 返回的字符串成为
a 的concat() 方法的参数,现在可以执行该方法。
就concat() 方法而言,结果与您编写c = a.concat("WORLD!") 相同——它不关心字符串“WORLD!”。由另一个函数创建。
您可以看出b.toUpperCase() 的返回值是作为参数传递的,而不是函数本身,因为末尾的括号函数名。函数名称后的括号告诉浏览器执行该函数,只要它计算出该函数的任何参数的值。 没有括号,函数被视为任何其他对象,并且可以作为参数或变量传递而无需实际执行任何操作。
当一个未执行的函数object被用作另一个函数的参数时,会发生什么完全取决于第二个函数内部的指令。如果您将该函数对象传递给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 确实 使用了第二个参数——它将其视为基数(“base” ) 的整数。)
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 函数使用它的方式工作。
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!”。
在这种情况下,返回值是一个与起始对象具有相同类型的 new 对象。在其他情况下,它可能是完全不同类型的数据。
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> 元素组成的new 选择,然后给它一个transform 属性。分配给变量 svg 的值是链中的 last 返回值。因此,在后面的代码中,您必须记住变量svg 实际上并不指代<svg> 元素的选择,而是指<g>。这让我感到困惑,所以我尽量避免使用变量名称 svg 来选择实际上不是 <svg> 元素的选择。
当然,任何 d3 .attr() 或 .style() 方法都可以将函数作为第二个参数而不是字符串。但这不会改变方法链接的工作方式。