【问题标题】:How are input parameters filled in javascript method chains?javascript方法链中的输入参数是如何填充的?
【发布时间】:2014-02-16 22:39:06
【问题描述】:

我正在尝试真正了解 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?

在 jquery 中,模式如下所示:

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

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

【问题讨论】:

  • 您实际上正在查看您提供的两个代码示例中的三个主要概念,方法链(methodA().B().C())、回调(匿名 First Class 函数,如 .methodA(function(data) {...}))和 Monands(@ 987654326@代码)。可能很难在一个问题中解决所有这些问题,但请查看this talk,了解 JavaScript 更强大的方面。祝你好运:)

标签: javascript jquery d3.js


【解决方案1】:

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

使用函数作为方法参数

首先,更正一下:您提供的示例不是“一个方法返回到另一个具有命名输入参数的方法”的示例。它们是一个函数作为另一个方法的输入参数的例子。

为了澄清,我将举一个例子,其中一个函数的返回值用作另一个函数的输入。

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

var c = a.concat( b.toUpperCase() ); //c = "Hello WORLD!"

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

  1. 浏览器开始解析concat方法,但需要弄清楚要给它什么参数。
  2. 该参数包含一个方法调用,因此执行btoUpperCase()方法,返回字符串“WORLD!”。
  3. 返回的字符串成为aconcat() 方法的参数,现在可以执行该方法。

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") 方法不会返回&lt;svg&gt; 元素的相同选择。它返回一个由&lt;g&gt; 元素组成的new 选择,然后给它一个transform 属性。分配给变量 svg 的值是链中的 last 返回值。因此,在后面的代码中,您必须记住变量svg 实际上并不指代&lt;svg&gt; 元素的选择,而是指&lt;g&gt;。这让我感到困惑,所以我尽量避免使用变量名称 svg 来选择实际上不是 &lt;svg&gt; 元素的选择。

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

【讨论】:

  • @akh2103 很高兴这是有道理的。你肯定不是唯一一个对所有匿名函数感到困惑的人——我已经链接到这个答案一次,因为另一个问题是有人无意中调用了一个函数而不是按名称传递它。
【解决方案2】:

如果我理解正确

这些数据指的是什么?怎么填的?

你的意思是它是如何工作的?这取决于如何调用回调。例如:

function Lib() {}

Lib.prototype.text = function(callback) {
  var data = 'hello world';
  callback(data); // `data` is the first parameter
  return this; // chain
};

var lib = new Lib();

lib.text(function(data){
  console.log(data); //=> "hello world"
});

【讨论】:

    【解决方案3】:

    d 对应于先前方法调用中.data(data).enter() 首次传入的数据集中的单个元素。正在发生的事情是 d3 隐式循环映射到数据的相应 dom 元素,因此数据驱动文档或 d3 的整个概念。

    也就是说,对于数据中的每个元素,当您调用 selectAll() 时,您应该期望看到附加到 dom 的元素数量与数据集中的元素数量相同。因此,当您调用.attr() 时,与 dom 中的元素对应的单个数据将传递给函数。当你阅读时

    .append("rect").attr("width",function(datum){})...
    

    您应该将其视为对绑定到您的选择的整个数据集中的一个元素的一次迭代。

    我有点不确定我是否回答了你的问题,因为它似乎是关于声明式/函数式编程以及方法链接的混合问题。上面的答案是关于 d3 的声明/功能方式。以下答案是关于方法链接的。

    这是一个很好的示例,它提供了对 Mike Bostock 在其精彩文章 http://bost.ocks.org/mike/chart/ 中概述的方法链的更多见解。

    我建议您通读此处 (https://github.com/mbostock/d3/wiki/API-Reference) 找到的 d3 api,以进一步了解每个函数的作用。例如 selection.filter 此外,我强烈建议您进入一个玩具示例,例如您提供的玩具示例,以进一步了解发生了什么。

    关于 d3 可重用图表的方法链接。

    function SomeChart()
    {
        var someProperty = "default value of some sort";
    
        function chartObject()
        {
        }
    
        chartObject.setSomeProperty(propertyValue)
        { 
         if (!arguments.length) return someProperty;
         someProperty = property;
         return chartObject;
        }
    
        return chartObject;
    }  
    
    
    
    var chart = SomehChart();
    

    此时的图表是什么?

    chart = chart.setSomeProperty("some special value");
    

    此时的图表是什么?

    chart = chart.setSomeProperty();
    

    此时的图表是什么?

    以下有什么区别?

    var chart = SomeChart().setSomeProperty("some special value");
    

    var chart = SomeChart();
    chart.setSomeProperty();
    

    答案是除了chart = chart.setSomeProperty();之外,它们都是一样的。

    【讨论】:

      【解决方案4】:

      在 d3 中 d3.select("body") 将返回 {search_element:document.body,selectAll:function,...} ,因此使用点符号调用前一个对象的可用函数。它可能只是返回类本身。因此,所有方法都可以按任何顺序与下一个点一起使用。但是在 ajax done 中,必须调用一些函数,以便它们填充 done 函数使用的对象的重要参数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-20
        • 2020-05-23
        • 2016-10-23
        相关资源
        最近更新 更多