在 JavaScript 中[] . forEach.call()做什么?

我查看了一些代码片段,发现有多个元素通过一个节点列表调用一个函数,其中一个 for 应用于一个空数组。

例如,我有这样的东西:

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

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

92215 次浏览

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

您可以找到 call函数 给你的文档。 forEach的文档是 给你

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

由于 NodeList类似于数组,因此数组方法实际上可以处理它,所以通过使用 [].forEach.call,您可以在 NodeList的上下文中调用 Array.prototype.forEach方法,就好像您能够简单地处理 yourNodeList.forEach(/*...*/)一样。

注意,空数组文字只是扩展版本的一个快捷方式,您可能会经常看到这种情况:

Array.prototype.forEach.call(/*...*/);

使用

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


});

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

[]是一个数组。
这个数组根本没有使用。

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

这只是比键入 Array.prototype.forEach.call(...);要快

接下来,forEach是一个函数,它接受一个函数作为输入..。

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

... 对于 this中的每个元素(其中 this是类似数组的,因为它有一个 length,你可以访问它的部分,如 this[1]) ,它将传递三个东西:

  1. 数组中的元素
  2. 元素的索引(第三个元素将传递 2)
  3. 对数组的引用

最后,.call是函数所拥有的原型(它是对其他函数调用的函数)。
.call将获取它的第一个参数,并用传递给 call的任何内容替换正则函数内部的 this,作为第一个参数(undefinednull将在日常 JS 中使用 window,或者将使用您传递的任何内容,如果处于“严格模式”)。其余的参数将传递给原始函数。

[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>顺序,您正在调用所提供的函数。

剪辑

合乎逻辑的结论/清理

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

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

那么,我们该如何处理这种差距呢? 因为格言显然是一个才华横溢、知识渊博的家伙,我想象自己知道自己在做什么/要去哪里(偶尔... ... 其他时候是头脑先学习) ?

答案其实很简单,鲍勃叔叔和克罗克福德爵士都会因为这个疏忽而捂住脸:

清理干净。

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


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

如果你怀疑自己是否需要这么做答案很可能是否定的。
现在每个具有高阶特性的(?)库都在做这样的事情。
如果你使用的是 loash 或者下划线,甚至是 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( )/array( )/etc 帮助方法将使那些想要使用列表就像他们使用数组一样的人们的生活变得更加容易(他们应该这样做) ,而且对于那些在不久的将来可以在 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 ];

超级干净,非常有用。
查找 休息传播操作符; 在 BabelJS 站点上尝试它们; 如果您的技术栈是有序的,在生产中使用它们与 Babel 和构建步骤。


没有理由不能使用从非数组到数组的转换... ... 只是不要把你的代码弄得一团糟,什么也不做 但是粘贴同样丑陋的行,到处都是。

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

使用给定的 this 值和提供的参数调用函数 个别地。 < a href = “ https://developer.mozilla.org/en/docs/JavaScript/Reference/Global _ Objects/function/call”rel = “ nofollow”> mdn

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

这是一个很好的代码示例,为了简单和清晰,应该对其进行重构。与其每次都使用 [].forEach.call()Array.prototype.forEach.call(),不如用一个简单的函数:

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

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

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

只要加上一句话:

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

瞧!

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

享受: ー)

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

NorGuard 解释了 什么 [].forEach.call()詹姆斯 · 阿拉迪斯 为什么的功能: 因为 querySelectorAll 返回一个 NodeList,而这个 NodeList没有一个 forEach 方法..。

除非你有像 Chrome 51 + ,Firefox 50 + ,Opera 38,Safari 10这样的现代浏览器。

如果没有,你可以添加一个 填充材料:

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);
}
};
}

只是一个快速和肮脏的解决方案,我总是最终使用。我不会碰原型机的,这是很好的练习。当然,有很多方法可以改善这种情况,但是你已经明白了。

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}`);
});

想更新一下这个老问题:

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

NodeList 对象是节点的集合,通常由 属性(如 Node.child 节点)和方法(如 QuerySelectorAll ().

尽管 NodeList 不是 Array,但是可以对它进行迭代 也可以使用 forEach () 将其转换为实际的 Array 数组来自()。

但是,一些较老的浏览器还没有实现 NodeList.foreach () 也不能使用 Array.from () Foreach ()ーー参见此文档的示例。

在这个页面上有很多好的信息(参见 回答 + 回答 + 评论) ,但是我最近有一个和 OP 相同的问题,需要一些挖掘才能得到整个图片。所以,这里有一个简短的版本:

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

一个较老的模式通过 Function.call()采用 Array 的方法,并使用数组文字([])而不是 Array.prototype,因为它更短于输入:

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

一个新的模式(2015年发布的 ECMAScript)是使用 Array.from():

Array.from(document.querySelectorAll('a')).forEach(a => {})
[].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
});

假设你有: const myList= document.querySelectorAll("p"); 这将返回 HTML 中包含所有 < p > 的列表/数组。 现在是 Array.prototype.forEach.call(myList, myCallback) 相当于 [].forEach.call(myList, myCallback) 其中“ myCallback”是一个回调函数。 您基本上是在 myList 的每个元素上运行回调函数。 希望这个对你有帮助!