[].forEach.call() 在 JavaScript 中有什么作用?

IT技术 javascript arrays foreach ecmascript-5 nodelist
2021-01-24 23:35:37

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

例如我有类似的东西:

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

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

6个回答

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

它被放在页面上,因为使用数组可以让您访问数组原型,例如.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将采用它的第一个参数并用this您传递的任何内容替换常规函数内部call,作为第一个参数(undefinednullwindow在日常 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>顺序,您正在调用提供的函数。

编辑

逻辑结论/清理

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

我想说的是,虽然.forEach比其同行少乐于助人,.map(transformer).filter(predicate).reduce(combiner, initialValue),它仍然成为目的时,你真正想要做的是改变外界(不是数组),n次,虽然能获得两种arr[i]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 或下划线甚至 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 ];

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


有没有好的理由不能够使用非数组转换成阵列......只是不要让你的代码乱七八糟的什么都不做,但粘贴同样丑陋线,无处不在。

@thetrystero 这正是重点。[]只是作为一个数组存在,以便您可以使用.call.forEach方法,并将 更改为this您想要处理的集合。.call看起来像这样:function (thisArg, a, b, c) { var method = this; method(a, b, c); }除了有内部黑魔法可以将this里面设置method为等于你传递的内容thisArg
2021-03-12 23:35:37
@MuhammadSaleh.call不会改变的valuethis的传递给里面有什么forEach.call改变的值this 的foreach内如果您对为什么this没有被传递forEach到您想要调用的函数感到困惑,您应该查看thisJavaScript 中的行为作为附录,只适用在这里(和地图/过滤/减少),有一个第二个参数forEach,这组this函数内arr.forEach(fn, thisInFn)[].forEach.call(arrLike, fn, thisInFn);或刚使用.bind;arr.forEach(fn.bind(thisInFn));
2021-03-14 23:35:37
简而言之...... Array.from()?
2021-03-14 23:35:37
现在我有点困惑,因为如果我这样做了:console.log(this); 我总是会得到 window 对象 我认为 .call 改变了 this 的值
2021-03-27 23:35:37
@LukeHutchison 机智,[].forEach.call(arr, fn)将运行arr, 传递到call; 不是在开头使用的数组forEach(如前两句所述,之后详细说明)。初始数组的长度完全无关紧要。当您使用forEach函数的第二个参数时,您正在使用索引;与将函数直接传递给forEach而不是call. 如果您记录第一个参数,您会得到"a", "b", "c",而不是1, 2, 3,因为call替换方式thisforEach工作方式。
2021-04-05 23:35:37

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

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

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

Array.prototype.forEach.call(/*...*/);
@MattFletcher:是的,没错。两者都可以工作,但为什么要把事情复杂化呢?只需直接在数组本身上调用该方法。
2021-03-20 23:35:37
因此,澄清一下,在使用标准数组的日常应用程序中使用[].forEach.call(['a','b'], cb)over没有任何优势['a','b'].forEach(cb),只是在尝试迭代没有forEach自己原型的类数组结构时那是对的吗?
2021-03-24 23:35:37
酷,谢谢。我不知道为什么,也许只是为了街头信誉,在 ALDI 给老太太留下深刻印象等等。我会坚持[].forEach():)
2021-03-28 23:35:37
var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) 效果很好
2021-04-01 23:35:37
@daslicht 不在旧版本的 IE 中!但是,确实支持forEach数组,但不支持NodeList 对象)
2021-04-08 23:35:37

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

这是一个很好的代码示例,为了简单和清晰,应该重构它。不要每次都使用[].forEach.call()or ,而是使用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
});

使用它可以更好地编写

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

});

它的作用是document.querySelectorAll('a')返回一个类似于数组的对象,但它不从Array类型继承所以我们forEachArray.prototype对象中调用该方法,并将上下文作为返回值document.querySelectorAll('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
});