如何在javascript方法链中填充输入参数?

ber*_*436 10 javascript jquery d3.js

我试图真正理解javascript如何工作的细节.在方法链接期间,有时一个方法返回到另一个具有命名输入参数的方法.

例如,在D3中,模式如下所示:

d3.select("body").selectAll("p")
    .data(dataset)
    .enter()
    .append("p")
    .text(function(d) { return d; }); //what does this d refer to? How is it filled?
Run Code Online (Sandbox Code Playgroud)

在jquery中,模式看起来像这样:

$.ajax({
  ...
})
  .done(function( data ) {  //what does this data refer to? How is it filled? 
Run Code Online (Sandbox Code Playgroud)

我从实际编码中知道这些输入参数的名称可以是任何东西.但是输入参数文件的数据来自哪里?它只是引用链中先前方法返回的数据吗?

Ame*_*aBR 17

两个不同的主题,所以我将分别解释它们:

使用函数作为方法参数

首先,更正:您提供的示例不是 "一个方法返回到另一个具有命名输入参数的方法"的示例.它们是将函数作为另一个方法的输入参数给出的示例.

为了澄清,我将举例说明一个函数的返回值用作另一个函数的输入.

var a = "Hello ",
    b = "World!";

var c = a.concat( b.toUpperCase() ); //c = "Hello WORLD!"
Run Code Online (Sandbox Code Playgroud)

为了创建c,按顺序发生以下情况:

  1. 浏览器开始解析concat方法,但需要弄清楚要给它的参数.
  2. 该参数包括方法调用,因此执行toUpperCase()方法b,返回字符串"WORLD!".
  3. 返回的字符串成为现在可以执行aconcat()方法的参数.

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]
Run Code Online (Sandbox Code Playgroud)

该函数parseFloat()是一个内置函数,它接受一个字符串并试图从中找出一个数字.请注意,当我将其传递给map函数时,我只是将其作为变量名传递,而不是在末尾用括号执行它.它由map函数执行,map函数决定它获取的参数.每个调用的结果parseFloatmap函数分配到它们在结果数组中的位置.

具体来说,map函数parseFloat使用三个参数执行:数组中的元素,数组中该元素的索引以及整个数组.parseFloat但是,该函数仅使用一个参数,因此忽略第二个和第三个参数.(parseInt但是,如果你尝试做同样的事情,你会得到意想不到的结果,因为parseInt 使用了第二个参数 - 它被视为整数的基数("基数").)

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;
};
Run Code Online (Sandbox Code Playgroud)

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"]
Run Code Online (Sandbox Code Playgroud)

但是,如果您只打算使用此功能myUpperCase一次,则无需单独声明它并为其命名.您可以直接将其用作匿名函数.

var stringArray = ["hello", "world", "again"];

var uppercaseStrings = stringArray.map( 
                           function(s,i,array) {
                               return s.toUpperCase();
                           }
                       );
   //uppercaseStrings still = ["HELLO", "WORLD", "AGAIN"]
Run Code Online (Sandbox Code Playgroud)

开始看起来很熟悉?这是许多d3和JQuery函数使用的结构 - 你传入一个函数,无论是作为函数名还是作为匿名函数,并且d3/JQuery方法在选择的每个元素上调用你的函数,传入指定值作为第一个,第二个和第三个参数.

那么参数名称怎么样?如你所说,它们可以是你想要的任何东西.我可以在我的函数中使用非常长的描述性参数名称:

function myUpperCase(stringElementFromArray, indexOfStringElementInArray, ArrayContainingStrings) { 

         return stringElementFromArray.toUpperCase();
}
Run Code Online (Sandbox Code Playgroud)

传递给函数的值将是相同的,完全基于map函数传递它们的顺序.实际上,因为我从不使用index参数或数组参数,所以我可以将它们从我的中删除函数声明,只需使用第一个参数.其他值仍会传入map,但会被忽略.但是,如果我使用传入的第二个或第三个参数map,我必须声明第一个参数的名称,只是为了保持编号直:

function indirectUpperCase (stringIDontUse, index, array) {

         return array[index].toUpperCase();
}
Run Code Online (Sandbox Code Playgroud)

这就是为什么,如果你想在d3中使用索引号,你必须写function(d,i){return i;}.如果你刚刚做function(i){return i;}了数值i对象的值,因为这就是d3函数总是作为第一个参数传递的,无论你怎么称呼它.它是传递参数值的外部函数.参数名称仅存在于内部函数内.


必要的警告和例外:

  • 我说map函数并不关心传入函数需要多少参数.这是真的,但是其他外部函数可以使用传入函数的.length属性来确定预期的参数数量并相应地传递不同的参数值.

  • 你不必命名为函数的参数为了访问传入的参数值.您也可以使用该arguments功能中的列表访问它们.因此,编写大写映射函数的另一种方法是:

    function noParamUpperCase() {
    
           return arguments[0].toUpperCase();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    但是,请注意,如果外部函数使用参数个数来确定要传递给内部函数的值,则此函数似乎不接受任何参数.


方法链接

你会注意到我在上面没有提到方法链.那是因为它是一个完全独立的代码模式,恰好在d3和JQuery中也经常使用.

让我们回到第一个创建"Hello WORLD!"的例子.超出"你好"和"世界!".如果你想创造"HELLO World"怎么办?代替?你可以做到

var a = "Hello ",
    b = "World!";

var c = a.toUpperCase().concat( b ); //c = "HELLO World!"
Run Code Online (Sandbox Code Playgroud)

在创建过程中发生的第一件事ca.toUpperCase()调用方法.该方法返回一个字符串("HELLO"),就像所有其他字符串都有一个.concat()方法一样.所以.concat(b)现在被调用作为返回字符串的方法,而不是原始字符串a.结果是b连接到大写版本的结尾a:"HELLO World!".

在这种情况下,返回的值是与起始对象相同类型的对象.在其他情况下,它可能是完全不同类型的数据.

var numberArray = [5, 15];

var stringArray = numberArray.toString().split(","); //stringArray = ["5", "15"]
Run Code Online (Sandbox Code Playgroud)

我们从一系列数字开始[5,15].我们调用数组的toString()方法,它生成一个格式良好的数组字符串版本:"5,15".此字符串现在具有所有可用的字符串方法,包括.split()将字符串拆分为围绕指定拆分字符的子字符串数组,在本例中为逗号.

您可以将此类型称为链接方法,调用另一个方法返回的值的方法.但是,当使用方法链接来描述Javascript库的一个特性时,关键方面是该方法的返回值与调用该方法的对象相同.

所以,当你这样做

d3.select("body").style("background", "green");
Run Code Online (Sandbox Code Playgroud)

d3.select("body")方法创建一个d3选择对象.该对象有一个style()方法.如果使用样式方法设置样式,则实际上不需要任何信息.它可能被设计为根本不返回任何值.相反,它返回方法所属的this对象(对象).所以你现在可以调用该对象的另一种方法.或者您可以将其分配给变量.或两者.

var body = d3.select("body").style("background", "green")
                            .style("max-width", "20em");
Run Code Online (Sandbox Code Playgroud)

但是,您始终必须了解返回同一对象的方法.例如,你看到很多d3代码示例

var svg = d3.select("svg").attr("height", "200")
                          .attr("width", "200")
                          .append("g")
                          .attr("transform", "translate(20,20)");
Run Code Online (Sandbox Code Playgroud)

现在,该方法append("g") 返回相同的<svg>元素选择.它返回一个由元素组成的选择<g>,然后给出一个transform属性.赋给变量svg的值是链中的最后一个返回值.因此,在以后的代码中,您必须记住变量svg实际上并不是指<svg>元素的选择,而是指向<g>.我发现这令人困惑,所以我尽量避免使用变量名svg来进行实际上不是<svg>元素的选择.

当然,这些d3 .attr().style()方法中的任何一个都可以将函数作为第二个参数而不是字符串.但这不会改变方法链的工作方式.