如何在JavaScript方法链中填充输入参数? [英] How are input parameters filled in javascript method chains?

查看:83
本文介绍了如何在JavaScript方法链中填充输入参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想真正了解JavaScript的工作细节。在方法链接中,有时一个方法返回另一个具有命名输入参数的方法。



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

  d3.select(body)selectAll(p)
.data(dataset)
.enter )
.append(p)
.text(function(d){return d;}); //这是指什么?如何填补?

在jquery中,模式如下所示:

  $。ajax({
...
})
.done(function(data){// this data referred to ?如何填充?



从实际编码中我知道这些输入参数的名称可以是任何。

解决方案

div

两个不同的主题,因此我将分别解释:



使用函数作为方法参数



首先,一个修正:你给的例子是不是例子,其中一个方法返回到另一个具有命名输入参数的方法。它们是一个函数作为输入参数到另一个方法。



为了说明,我将给出一个示例,其中一个函数的返回值用作另一个方法的输入。

  var a =Hello,
b =World!

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

为了创建 c 按顺序发生:


  1. 浏览器开始解析 concat 方法,

  2. 该参数包含方法调用,因此 toUpperCase()方法<$返回字符串WORLD!。

  3. 返回的字符串变为 a 的参数 code>' concat()方法,现在可以执行。

对于 concat()方法,结果是就像你写 c = a.concat(WORLD!) - 它不关心字符串WORLD!

可以判断 b.toUpperCase c $ c>作为参数传递,而不是函数本身,因为函数名称末尾有括号。函数名后的圆括号告诉浏览器执行该函数,只要它已经计算出该函数的任何参数的值。 没有括号,函数被视为任何其他对象,并且可以作为参数或变量传递,而不实际执行任何操作。



当未执行的函数 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函数,它看起来像这样:

  Array.prototype.myMap = function(f) {

var result = [];

for(var i = 0,n = this.length; i
result [i] = f(this [i]这个);
//使用三个参数调用传入的函数
}
return result;
};

调用 f 函数,参数,不知道什么是什么或它做什么。参数以特定顺序给出 - 元素,索引,数组 - 但不与任何特定的参数名称相关联。



现在, map 方法是有很少的Javascript函数可以直接调用,只是传递一个值作为参数。大多数函数是特定对象的方法。例如,我们不能使用 toUpperCase 方法作为 map 的参数,因为 toUpperCase 仅作为字符串对象的方法存在,并且仅作用于该特定字符串对象,而不是映射函数可能赋予它的任何参数。为了将字符串数组映射为大写,您需要创建自己的函数,以映射函数的方式使用它。

  var stringArray = [hello,world,again]; 

function myUpperCase(s,i,array){

return s.toUpperCase(); //调用传入的字符串的方法

}

var uppercaseStrings = stringArray.map(myUpperCase);
// uppercaseStrings = [HELLO,WORLD,AGAIN]

但是,如果你只需要使用函数 myUpperCase 这一次,你不需要单独声明它,并给它一个名字。您可以直接使用它作为匿名函数

  var stringArray = [hello世界,再次]; 

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 传递的第二个或第三个参数,我必须声明第一个参数的名称,保持编号直接:

  function indirectUpperCase(stringIDontUse,index,array){

return array [index] .toUpperCase();
}

这就是为什么,如果你想使用d3中的索引号,写 function(d,i){return i;} 。如果你只是 function(i){return i;} ,则 i 的值将是数据对象,因为这就是d3函数总是作为第一个参数传递,不管你叫什么。它是在参数的值传递的外部函数。






必要的注意事项和例外:




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

    / li>
  • 您不能 命名函数的参数,以访问传入的参数值。您还可以使用该函数中的 arguments 列表访问它们。因此,编写大写映射函数的另一种方法是:

     函数noParamUpperCase(){

    return arguments [0] .toUpperCase();
    }

    但是,请注意,如果外部函数使用参数数量来确定传递给内部函数的值,此函数将不会接受任何参数。







方法链



你会注意到上面没有提到方法链。这是因为它是一个完全独立的代码模式,刚好也在d3和JQuery中使用很多。



让我们回到第一个例子,它创建了Hello世界!出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!的大写版本的结尾。 / p>

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

  var numberArray = [5,15] 

var stringArray = numberArray.toString()。split(,); // stringArray = [5,15]



我们从一个数字数组开始, [5,15] 。我们调用数组的 toString()方法,它产生一个格式化的字符串版本的数组:5,15。这个字符串现在具有所有可用的字符串方法,包括 .split(),它将一个字符串分割成指定分割字符(在这种情况下是逗号)周围的子字符数组。



您可以将此称为一种方法链接,调用由另一个方法返回的值的方法。然而,当使用方法链接来描述Javascript库的特征时,关键方面是方法的返回值是调用方法的相同对象。



当你做

  d3.select(body , 绿色); 

d3.select(body)方法创建一个d3选择对象。该对象有一个 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 20));

现在,方法 append(g) 不返回与< svg> 元素相同的选择。它返回由< g> 元素组成的选择,然后给出一个transform属性。赋给变量 svg 的值是链中的最后一个返回值。所以在后面的代码中,你必须记住变量 svg 实际上并不指向< svg> 元素,但到< g> 。我觉得很混乱,所以我尽量避免使用变量名 svg 的选择,实际上不是一个< svg> 元素。



当然,任何d3 .attr() $ c> .style()方法可能使用一个函数作为第二个参数,而不是一个字符串。但这不会改变方法链接的工作方式。


I am trying to really understand the details of how javascript works. During method chaining, sometimes one method returns to another method that has a named input parameter.

For instance, in D3, the pattern looks like this:

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?

In jquery, the pattern looks like this:

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

I know from practical coding that the name of these input parameter can be anything. But where does the data that files the input parameter come from? Does it just refer to the data returned from the prior method in the chain?

解决方案

Two different topics, so I'll explain them separately:

Using functions as method parameters

First, a correction: The examples you are giving are not examples where "one method returns to another method that has a named input parameter". They are examples where a function is given as the input parameter to another method.

To clarify, I'll give you an example where the return value of one function is used as the input to another.

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

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

In order to create c, the following happens, in order:

  1. The browser starts to parse the concat method, but needs to figure out what parameter to give it.
  2. That parameter includes a method call, so the toUpperCase() method of b is executed, returning the string "WORLD!".
  3. The returned string becomes the parameter for a's concat() method which can now be executed.

As far as the concat() method is concerned, the result is the same as if you wrote c = a.concat("WORLD!") -- it doesn't care that the string "WORLD!" was created by another function.

You can tell that the returned value of b.toUpperCase() is being passed as the parameter, and not the function itself, because of the parentheses at the end of the function name. Parentheses after a function name tell the browser to execute that function, just as soon as it has figured out the values of any parameters to that function. Without the parentheses, the function is treated as any other object, and can be passed around as a parameter or variable without actually doing anything.

When a function object, unexecuted, is used as the parameter for another function, what happens is entirely dependent on the instructions inside that second function. If you pass that function object to console.log(), the string representation of the function will be printed to the console without ever executing the function you passed in. However, most methods that accept another function as input are designed to call that function with specified parameters.

One example is the map() method of arrays. The map method creates a new array in which every element is the result of running the mapping function on the corresponding element of the original array.

var stringArray = ["1", "2!", "3.0", "?"];

var numberArray = stringArray.map(parseFloat); //numberArray = [1, 2, 3, NaN]

The function parseFloat() is a built-in function that takes a string and tries to figure out a number from it. Note that when I pass it in to the map function, I'm just passing it in as a variable name, not executing it with parentheses at the end. It is executed by the map function, and it is the map function that decides what parameters it gets. The results of each call to parseFloat are assigned by the map function to their place in the result array.

Specifically, the map function executes parseFloat with three parameters: the element from the array, the index of that element in the array, and the array as a whole. The parseFloat function only uses one parameter, however, so the second and third parameters are ignored. (If you try to do the same thing with parseInt, however, you'll get unexpected results because parseInt does use a second parameter -- which it treats as the radix ("base") of the integer.)

The map function doesn't care how many parameters the passed-in function is expecting, and it certainly doesn't care which variable names are used inside that function. If you were writing the map function yourself, it would look something like this:

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 function is called, and given parameters, without knowing anything about what it is or what it does. The parameters are given in a specific order -- element, index, array -- but are not linked to any particular parameter name.

Now, a limitation of something like the map method is that there are very few Javascript functions which can be called directly, just passing a value as a parameter. Most functions are methods of a specific object. For example, we couldn't use the toUpperCase method as a parameter to map, because toUpperCase only exists as a method of a string object, and only acts on that particular string object, not on any parameter that the map function might give it. In order to map an array of strings to uppercase, you need to create your own function that works in the way the map function will use it.

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"]

However, if you're only ever going to use the function myUpperCase this once, you don't need to declare it separately and give it a name. You can use it directly as an anonymous function.

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

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

Starting to look familiar? This is the structure used by so many d3 and JQuery functions -- you pass-in a function, either as a function name or as an anonymous function, and the d3/JQuery method calls your function on each element of a selection, passing in specified values as the first, second and maybe third parameter.

So what about the parameter names? As you mentioned, they can be anything you want. I could have used very long and descriptive parameter names in my function:

function myUpperCase(stringElementFromArray, indexOfStringElementInArray, ArrayContainingStrings) { 

         return stringElementFromArray.toUpperCase();
}

The values that get passed in to the function will be the same, based purely on the order in which the map function passes them in. In fact, since I never use the index parameter or the array parameter, I can leave them out of my function declaration and just use the first parameter. The other values still get passed in by map, but they are ignored. However, if I wanted to use the second or third parameter passed in by map, I would have to declare a name for the first parameter, just to keep the numbering straight:

function indirectUpperCase (stringIDontUse, index, array) {

         return array[index].toUpperCase();
}

That's why, if you want to use the index number in d3, you have to write function(d,i){return i;}. If you just did function(i){return i;} the value of i would be the data object, because that's what the d3 functions always pass as the first parameter, regardless of what you call it. It's the outside function that passes in the values of the parameters. The parameter names only exist inside the inner function.


Requisite caveats and exceptions:

  • I said that the map function doesn't care how many parameters a passed-in function expects. That's true, but other outer functions could use the passed-in function's .length property to figure out how many parameters are expected and pass different parameter values accordingly.

  • You don't have to name arguments for a function in order to access passed-in parameter values. You can also access them using the arguments list inside that function. So another way of writing the uppercase mapping function would be:

    function noParamUpperCase() {
    
           return arguments[0].toUpperCase();
    }
    

    However, note that if an outer function is using the number of parameters to determine what values to pass to the inner function, this function will appear not to accept any arguments.


Method Chaining

You'll notice that nowhere above did I mention method chaining. That's because it's a completely separate code pattern, that just happens to also be used a lot in d3 and JQuery.

Let's go back to the first example, which created "Hello WORLD!" out of "Hello " and "World!". What if you wanted to create "HELLO World!" instead? You could do

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

var c = a.toUpperCase().concat( b ); //c = "HELLO World!"

The first thing that happens in the creation of c is the a.toUpperCase() method gets called. That method returns a string ("HELLO "), which like all other strings has a .concat() method. So .concat(b) is now getting called as a method of that returned string, not of the original string a. The result is that b gets concatenated to the end of the uppercase version of a: "HELLO World!".

In that case, the returned value was a new object of the same type as the starting object. In other cases, it could be a completely different type of data.

var numberArray = [5, 15];

var stringArray = numberArray.toString().split(","); //stringArray = ["5", "15"]

We start with an array of numbers, [5,15]. We call the array's toString() method, which produces a nicely formatted string version of the array: "5,15". This string now has all it's string methods available, including .split(), which splits a string into an array of substrings around a specified split character, in this case the comma.

You could call this a type of method chaining, calling a method of a value returned by another method. However, when method chaining is used to describe a feature of a Javascript library, the key aspect is that the returned value of the method is the same object that called the method.

So when you do

d3.select("body").style("background", "green");

The d3.select("body") method creates a d3 selection object. That object has a style() method. If you use the style method to set a style, you don't really need any information back from it. It could have been designed not to return any value at all. Instead, it returns the object that the method belongs to (the this object). So you can now call another method of that object. Or you could assign it to a variable. Or both.

var body = d3.select("body").style("background", "green")
                            .style("max-width", "20em");

However, you always have to be aware of the methods which don't return the same object. For example, in a lot of d3 code examples you see

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

Now, the method append("g") doesn't return the same selection of the <svg> element. It returns a new selection consisting of the <g> element, which is then given a transform attribute. The value that gets assigned to the variable svg is the last return value from the chain. So in later code, you would have to remember that the variable svg doesn't actually refer to a selection of the <svg> element, but to the <g>. Which I find confusing, so I try to avoid ever using the variable name svg for a selection that isn't actually an <svg> element.

Of course, any of those d3 .attr() or .style() methods could have taken a function as the second parameter instead of a string. But that wouldn't have changed how method chaining works.

这篇关于如何在JavaScript方法链中填充输入参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆