155

我正在查看一些代码片段,发现多个元素在节点列表上调用函数,并将 forEach 应用于空数组。

例如我有类似的东西:

[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

但我不明白它是如何工作的。谁能解释一下 forEach 前面的空数组的行为以及它是如何call工作的?

4

12 回答 12

233

[]是一个数组。
这个数组根本不用。

它被放在页面上,因为使用数组可以让您访问数组原型,例如.forEach.

这比打字快Array.prototype.forEach.call(...);

接下来,forEach是一个将函数作为输入的函数......

[1,2,3].forEach(function (num) { console.log(num); });

...对于每个元素this(其中this类似于数组,因为它有 alength并且您可以访问它的部分,如this[1]),它将传递三件事:

  1. 数组中的元素
  2. 元素的索引(第三个元素将通过2
  3. 对数组的引用

最后,.call是函数具有的原型(它是在其他函数上调用的函数)。
.call将采用它的第一个参数并用this您传递的任何内容替换常规函数内部call,作为第一个参数(undefined或者nullwindow在日常 JS 中使用,或者将是您传递的任何内容,如果在“严格模式”下)。其余参数将传递给原始函数。

[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
    console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"

因此,您正在创建一种调用forEach函数的快速方法,并且您正在this从空数组更改为所有<a>标签的列表,并且对于每个<a>顺序,您正在调用提供的函数。

编辑

逻辑结论/清理

下面有一篇文章的链接,建议我们放弃函数式编程的尝试,每次都坚持手动的内联循环,因为这种解决方案是 hack-ish 且难看。

我想说虽然.forEach它的帮助不如它的对应物 , .map(transformer), .filter(predicate).reduce(combiner, initialValue)但当你真正想做的只是修改外部世界(而不是数组)n 次,同时可以访问arr[i]or时,它仍然有用i

那么我们如何处理这种差异,因为 Motto 显然是一个有才华和知识渊博的人,我想想象我知道我在做什么/我要去哪里(不时......其他次是头头学习)?

答案实际上很简单,由于疏忽,鲍勃叔叔和克罗克福德爵士都会面对:

清理它

function toArray (arrLike) { // or asArray(), or array(), or *whatever*
  return [].slice.call(arrLike);
}

var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);

现在,如果您质疑自己是否需要这样做,答案很可能是否定的……
这件事是由……现在每个(?)具有高阶特性的库完成的。
如果您使用 lodash 或 underscore 甚至 jQuery,它们都会有一种获取一组元素并执行 n 次操作的方法。
如果你不使用这样的东西,那么一定要自己写。

lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
  var others = lib.array(arguments, 1);
  return others.reduce(appendKeys, subject);
};

ES6(ES2015) 及更高版本的更新

slice( )/ /etc 辅助方法不仅array( )可以让那些想要像使用数组一样使用列表的人生活更轻松(因为他们应该),而且对于那些可以在相对较近的 ES6+ 浏览器中操作的人来说也是如此。未来,或者今天在 Babel 中的“转译”,你有内置的语言特性,这使得这种类型的事情变得不必要。

function countArgs (...allArgs) {
  return allArgs.length;
}

function logArgs (...allArgs) {
  return allArgs.forEach(arg => console.log(arg));
}

function extend (subject, ...others) { /* return ... */ }


var nodeArray = [ ...nodeList1, ...nodeList2 ];

超级干净,非常好用。
查找RestSpread运算符;在 BabelJS 网站上试用它们;如果您的技术堆栈是有序的,请使用 Babel 和构建步骤在生产中使用它们。


没有充分的理由不能使用从非数组到数组的转换......只是不要把你的代码弄得一团糟,除了在任何地方粘贴同样丑陋的行之外什么都不做。

于 2013-04-17T06:58:20.423 回答
59

querySelectorAll方法返回 a NodeList,它类似于数组,但不完全是数组。因此,它没有forEach方法(哪些数组对象通过 继承Array.prototype)。

由于 aNodeList类似于数组,因此数组方法实际上将对其起作用,因此通过使用,您可以在 的上下文中[].forEach.call调用该方法,就好像您能够简单地执行.Array.prototype.forEachNodeListyourNodeList.forEach(/*...*/)

请注意,空数组文字只是扩展版本的快捷方式,您可能也会经常看到:

Array.prototype.forEach.call(/*...*/);
于 2013-04-17T06:52:45.440 回答
21

其他答案已经很好地解释了这段代码,所以我只是添加一个建议。

这是一个很好的代码示例,应该为了简单和清晰而重构。不要使用[].forEach.call()orArray.prototype.forEach.call()每次执行此操作,而是使用它创建一个简单的函数:

function forEach( list, callback ) {
    Array.prototype.forEach.call( list, callback );
}

现在你可以调用这个函数而不是更复杂和晦涩的代码:

forEach( document.querySelectorAll('a'), function( el ) {
   // whatever with the current node
});
于 2013-04-17T07:00:33.950 回答
6

使用它可以更好地编写

Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {

});

它所做的是document.querySelectorAll('a')返回一个类似于数组的对象,但它不从Array类型继承。所以我们forEach从对象调用方法,Array.prototype上下文作为返回的值document.querySelectorAll('a')

于 2013-04-17T06:55:29.580 回答
4
[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

它与以下内容基本相同:

var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
   // whatever with the current node
});
于 2020-05-21T19:21:16.507 回答
2

一个空数组forEach在其原型中有一个属性,它是一个 Function 对象。(空数组只是获取对forEach所有Array对象都具有的函数的引用的一种简单方法。)函数对象又具有一个call也是函数的属性。当您调用函数的call函数时,它会使用给定的参数运行该函数。第一个参数成为this被调用函数。

您可以在此处找到该call函数的文档。文档在这里forEach

于 2013-04-17T06:51:34.490 回答
2

此页面上有很多很好的信息(请参阅答案+答案+评论),但我最近遇到了与 OP 相同的问题,并且需要进行一些挖掘才能获得全貌。所以,这是一个简短的版本:

目标是在本身没有这些方法Array的类数组上使用方法。NodeList

一个较旧的模式通过 选择了 Array 的方法Function.call(),并使用了数组文字 ( []),而不是Array.prototype因为它的类型更短:

[].forEach.call(document.querySelectorAll('a'), a => {})

一种较新的模式(ECMAScript 2015 后)是使用Array.from()

Array.from(document.querySelectorAll('a')).forEach(a => {})
于 2020-05-20T18:39:39.393 回答
2

想更新这个老问题:

[].foreach.call()在现代浏览器中使用循环元素的原因已经基本结束。我们可以document.querySelectorAll("a").foreach()直接使用。

NodeList 对象是节点的集合,通常由 Node.childNodes 等属性和 document.querySelectorAll() 等方法返回。

尽管 NodeList 不是数组,但可以使用 forEach() 对其进行迭代。也可以使用 Array.from() 将其转换为真正的 Array。

但是,一些较旧的浏览器没有实现 NodeList.forEach() 或 Array.from()。这可以通过使用 Array.prototype.forEach() 来规避——请参阅本文档的示例。

于 2018-11-27T18:45:03.610 回答
1

只需添加一行:

NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;

瞧!

document.querySelectorAll('a').forEach(function(el) {
  // whatever with the current node
});

享受 :-)

警告: NodeList 是一个全局类。如果您编写公共图书馆,请不要使用此建议。但是,当您在网站或 node.js 应用程序上工作时,这是提高自我效能的一种非常方便的方法。

于 2015-09-18T05:42:34.430 回答
1

只是一个快速而肮脏的解决方案,我总是最终使用。我不会碰原型,就像好的做法一样。当然,有很多方法可以让它变得更好,但你明白了。

const forEach = (array, callback) => {
  if (!array || !array.length || !callback) return
  for (var i = 0; i < array.length; i++) {
    callback(array[i], i);
  }
}

forEach(document.querySelectorAll('.a-class'), (item, index) => {
  console.log(`Item: ${item}, index: ${index}`);
});
于 2018-07-02T05:59:44.637 回答
0

[]总是返回一个新数组,它等效于new Array()但保证返回一个数组,因为Array可以被用户覆盖而[]不能。所以这是获取 的原型的安全方法Array,然后如前所述,call用于在类似数组的节点列表 (this) 上执行函数。

使用给定的 this 值和单独提供的参数调用函数。mdn

于 2013-04-17T07:00:03.120 回答
0

Norguard解释了做什么 [].forEach.call()James Allardice 为什么我们这样做:因为 querySelectorAll 返回一个NodeList没有 forEach 方法的...

除非您拥有现代浏览器,例如 Chrome 51+、Firefox 50+、Opera 38、Safari 10。

如果没有,您可以添加Polyfill

if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (var i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}
于 2017-09-27T10:51:21.617 回答