两个不同的主题,所以我将分别解释它们:
使用函数作为方法参数
首先,更正:您给出的例子是not“一个方法返回到具有命名输入参数的另一个方法”的示例。它们是一个函数作为另一个方法的输入参数的示例。
为了澄清这一点,我将举一个例子,其中一个函数的返回值is用作另一个的输入。
var a = "Hello ",
b = "World!";
var c = a.concat( b.toUpperCase() ); //c = "Hello WORLD!"
为了创造c
,按顺序发生以下情况:
- 浏览器开始解析
concat
方法,但需要弄清楚给它什么参数。
- 该参数包括方法调用,因此
toUpperCase()
的方法b
执行,返回字符串“WORLD!”。
- 返回的字符串成为参数
a
's concat()
现在可以执行的方法。
就concat()
就方法而言,结果与您编写的结果相同c = a.concat("WORLD!")
-- 它并不关心字符串“WORLD!”是由另一个函数创建的。
你可以说返回值 of b.toUpperCase()
作为参数传递,并且not函数本身,因为函数名称末尾有括号。函数名称后面的括号告诉浏览器在计算出该函数的任何参数的值后立即执行该函数。Without在括号中,该函数被视为任何其他对象,并且可以作为参数或变量传递,而无需实际执行任何操作。
当一个函数object未执行的,用作另一个函数的参数,发生的情况完全取决于第二个函数内的指令。如果您将该函数对象传递给console.log()
,函数的字符串表示形式将被打印到控制台,而不执行您传入的函数。但是,大多数接受另一个函数作为输入的方法都被设计为使用指定参数调用该函数。
一个例子是map()
数组的方法。这map
方法创建一个新数组,其中每个元素都是对原始数组的相应元素运行映射函数的结果。
var stringArray = ["1", "2!", "3.0", "?"];
var numberArray = stringArray.map(parseFloat); //numberArray = [1, 2, 3, NaN]
功能parseFloat()
是一个内置函数,它接受一个字符串并尝试从中计算出一个数字。请注意,当我将其传递给映射函数时,我只是将其作为变量名传递,而不是在末尾加上括号来执行它。它由map函数执行,并且是map函数决定它获取什么参数。每次调用的结果parseFloat
被分配的map
函数到结果数组中的位置。
具体来说,map函数执行parseFloat
with three参数:数组中的元素、该元素在数组中的索引以及整个数组。这parseFloat
然而,函数仅使用一个参数,因此第二个和第三个参数将被忽略。 (如果你尝试做同样的事情parseInt
,但是,您会得到意想不到的结果,因为parseInt
does使用第二个参数——它将其视为整数的基数(“基数”)。)
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;
};
The f
函数被调用并给出参数,而不知道它是什么或它做什么。参数以特定的顺序给出——元素、索引、数组——但不链接到任何特定的参数名称。
现在,像这样的限制map
方法的缺点是很少有Javascript函数可以直接调用,只是传递一个值作为参数。大多数函数都是特定对象的方法。例如,我们不能使用toUpperCase
方法作为参数map
, 因为toUpperCase
仅作为字符串对象的方法存在,并且仅作用于该特定字符串对象,而不作用于映射函数可能为其提供的任何参数。为了将字符串数组映射为大写,您需要创建自己的函数,该函数以映射函数使用它的方式工作。
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
,但它们被忽略了。然而,如果我wanted使用传入的第二个或第三个参数map
,我必须为第一个参数声明一个名称,只是为了保持编号的直接性:
function indirectUpperCase (stringIDontUse, index, array) {
return array[index].toUpperCase();
}
这就是为什么,如果你想使用 d3 中的索引号,你必须写function(d,i){return i;}
。如果你刚刚这样做了function(i){return i;}
的价值i
将是数据对象,因为这就是 d3 函数始终作为第一个参数传递的内容,无论您如何称呼它。这是外部函数传入参数值。参数名称仅存在于内部函数内部。
必要的警告和例外:
我说过,map 函数并不关心传入函数需要多少个参数。确实如此,但其他外部函数可以使用传入函数的.length
属性来确定需要多少个参数并相应地传递不同的参数值。
-
你不have命名函数的参数以便访问传入的参数值。您还可以使用以下方式访问它们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
is the a.toUpperCase()
方法被调用。该方法返回一个字符串(“HELLO”),与所有其他字符串一样,它有一个.concat()
方法。所以.concat(b)
现在被称为该方法返回的字符串,不是原始字符串a
。结果是b
连接到大写版本的末尾a
: “你好世界!”。
在这种情况下,返回值是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");
The d3.select("body")
方法创建一个 d3 选择对象。该对象有一个style()
方法。如果你使用 style 方法set一种样式,您实际上不需要从中返回任何信息。它本可以被设计为根本不返回任何值。相反,它返回该方法所属的对象(this
目的)。所以您现在可以调用该对象的另一个方法。或者你可以将它分配给一个变量。或两者。
var body = d3.select("body").style("background", "green")
.style("max-width", "20em");
但是,您始终必须了解哪些方法don't返回相同的对象。例如,在很多 d3 代码示例中您会看到
var svg = d3.select("svg").attr("height", "200")
.attr("width", "200")
.append("g")
.attr("transform", "translate(20,20)");
现在,该方法append("g")
doesn't返回相同的选择<svg>
元素。它返回一个new选择包括<g>
元素,然后赋予它一个转换属性。分配给变量的值svg
is the last从链中返回值。所以在后面的代码中,你必须记住变量svg
实际上并不是指选择<svg>
元素,但对于<g>
。我觉得这很令人困惑,所以我尽量避免使用变量名svg
对于实际上不是的选择<svg>
元素。
当然,任何 d3.attr()
or .style()
方法可以将函数而不是字符串作为第二个参数。但这不会改变方法链的工作方式。