JavaScript 调用 - 我可以直接访问对象属性

JavaScript call - I can just access object properties directly

我想了解使用 call() 的目的。我读this example on what it's used for, and gathered examples from Mozilla,即:

var animals = [
  { species: 'Lion', name: 'King' },
  { species: 'Whale', name: 'Fail' }
];

for (var i = 0; i < animals.length; i++) {
  (function(i) {
    this.print = function() {
      console.log('#' + i + ' ' + this.species
                  + ': ' + this.name);
    }
    this.print();
  }).call(animals[i], i);
}

我知道他们的例子只是为了演示,但我可以很容易地直接在循环中访问对象的属性:

        var animals = [
          { species: 'Lion', name: 'King' },
          { species: 'Whale', name: 'Fail' }
        ];

        for (var i = 0; i < animals.length; i++) {
            console.log('#' + i + ' ' + animals[i].species  + ': ' + animals[i].name);        
        }

由此我感到困惑。我仍然不明白我需要将不同的 this 上下文传递给 call() 的原因。


此外,对于那个 mozilla 示例,在 .call(animals[i], i); 函数中,我有两个问题:


这个问题是由我试图理解的以下代码段引发的。此代码用于收集文档中某些范围内的所有内部值并将它们连接在一起。为什么我要在地图这里执行call?是因为 document.querySelectorAll 不符合上下文吗?

        console.log([].map.call(document.querySelectorAll("span"), 
            function(a){ 
                return a.textContent;
            }).join(""));

我将从你问题的底部开始,询问为什么在此代码中使用 "call":

    console.log([].map.call(document.querySelectorAll("span"), 
        function(a){ 
            return a.textContent;
        }).join(""));

那么,问题来了。这段代码真正想做的是在数组上使用 map 方法。但是,document.querySelectorAll return 是一个 NodeList,而不是数组。最重要的是,NodeList not 有一个映射函数。所以你会被经典的 for 循环困住。

然而,事实证明 Array.map 实际上适用于任何长度为 属性 并支持数字索引(例如 nodes[i])的东西。但是,地图的实现在内部专门使用 this.length

因此,通过在此处使用 call,您可以借用 Array.map 的实现,向其传递一个 this 引用,该引用实际上不是一个数组,但可以像一个数组一样工作为这些目的而工作。

所以基本上,这是调用:

document.querySelectorAll("span").map( 
  function(a){ 
    return a.textContent;
  }).join("");

除了return实际上没有map方法,所以我们从Array中借用了一个。这通常被称为 "duck typing"。

动物示例,就像其中包含动物的任何示例一样,相当做作。您在调用中传递 i 是因为您调用的函数(匿名函数)需要一个参数(注意 function(i)),因此您需要传递一个值。正如我所说,这是做作的,在现实生活中你不会这样做。

所以一般来说,调用最有用的方法是借用一种类型的方法以用于另一种类型。

document.querySelectorAll("span") returns 一个 NodeList 而不是一个数组,所以如果你尝试 document.querySelectorAll("span").map(...) 你会得到一个关于 map 没有被定义的错误(如果你尝试.join)

[].map.call(document.querySelectorAll("span"), 
    function(a){ 
        return a.textContent;
    }
)

这只是使用 call 来实现,因此 NodeList 用作 map 函数的数组


for (var i = 0; i < animals.length; i++) {
  (function(i) {
    this.print = function() {
      console.log('#' + i + ' ' + this.species
                  + ': ' + this.name);
    }
    this.print();
  }).call(animals[i], i);
}
  1. 这里的this是动物数组。我猜你需要在这里调用,因为 animals 超出了内部匿名函数的范围?

    实际上这里的 this 将引用 animals 数组中索引为 i 的对象。但是 animals 并没有超出范围,只是 this.speciesanimals[i].species 更容易。他们本可以轻松完成 )(i)animals[i].species 而不是 ).call(...)this.species

  2. 将索引 i 作为第二个参数传入 .call(animals[i], i); 的目的是什么?

    由于他们使用的是匿名函数,如果他们不使用 IIFE 并传入索引 i 参数,匿名函数将使用 for 循环的增量变量 i 的任何值最后设置为。所以打印函数将记录相同的信息而不是每个对象的信息。

您的示例与 Mozilla 的示例不同。在 Mozilla 中,它创建了一个函数。如果循环中没有匿名函数来创建该函数,您将遇到 closure problem。实际上意味着该功能不会像您预期的那样运行。例如,尝试重新创建没有匿名函数的函数并调用 animals[0].print()

the this here is the animals array. I'm guessing you need call here because animals is otherwise out of scope to the inner anonymous function?

这个this值是一个单独的动物对象。 animals 数组不超出内部匿名函数的范围 - 这是 JavaScript 的性质,您将始终可以访问外部函数的变量,除非 another variable is shadowing it(具有相同的名称从而覆盖了一个外部变量)。

考虑没有调用方法的例子:

var animals = [
    { species: 'Lion', name: 'King' },
    { species: 'Whale', name: 'Fail' }
];

for (var i = 0; i < animals.length; i++) {
    (function(i) {
        this.print = function() {
            console.log('#' + i + ' ' + this.species
              + ': ' + this.name);
        }
        this.print();
    })(i);
}

在此示例中,this 变量将引用 window 对象,或 null 取决于某些因素,例如是否使用 use strict。但是我们要做的是给动物对象附加print方法,所以需要用到call方法

what is the purpose of passing in the index i as the second argument in .call(animals[i], i);?

如上,是因为闭包问题,你不想在外层闭包中引用i变量,而是想将它定位到匿名函数内部,这样print 方法很正常。

那个例子是正确的,但是很差,完全没有必要达到他们的结果。

Also, with that mozilla example, in the .call(animals[i], i); function, I have two questions:

  • the this here is the animals array. I'm guessing you need call here because animals is otherwise out of scope to the inner anonymous function?

实际上,this 是每个项目。在 .call(animals[i], i); 中,第一项是将映射到 this 的对象,因此函数内的 this 是当前动物项,而不是动物数组。

此外,内部函数可以在当前闭包中访问 animals 就好了。再次证明他们的榜样很差

  • what is the purpose of passing in the index i as the second argument in .call(animals[i], i);?

第二个参数是call传递给被调用函数的第一个参数(第三个参数是第二个参数,依此类推)。 这与 apply 相反,后者将数组作为第二个参数并将其作为单独的参数应用。 但是,他们调用的函数可以访问当前的 i 一次再次表明他们的例子是不必要的。


现在,第二部分。您需要使用 call,因为 document.querySelectorAllNodeList 而不是 Array,不幸的是,NodeList 没有 map 方法,而Array 确实如此。

对于一个非常简化的版本,假设我们 Array.prototype.map 定义了如下内容:

Array.prototype.map = function(fn) {
  var copy = [];
  for (var i = 0; i < this.length; i++) {
    copy.push(fn(this[i]));
  }
  return copy;
};

你可以在这里看到当我们调用:

var timesTwo = [1,2,3].map(function(n) {
  return n * 2;
});
// timesTwo is [2,4,6];

您可以在我们定义的 Array.prototype.map 中看到我们将我们的数组实例称为 this。现在,回到上面的 NodeList:我们没有可用的 map 方法,但我们仍然可以使用 call 和强制 this 调用 Array.prototype.map参考我们的NodeList。这很好,因为它确实有一个 length 属性,正如我们正在使用的那样。

因此,我们可以使用:

var spanNodeList = document.querySelectorAll('span');
Array.prototype.map.call(spanNodeList, function(span) { /* ... */ });

// Or, as MDN's example, use an array instance as "[]"
[].map.call(spanNodeList, function(span) { /* ... */ });

希望对您有所帮助。