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


134

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

例如,我有类似的东西:

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

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

Answers:


202

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

之所以放在页面上,是因为使用数组使您可以访问数组原型,例如.forEach

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

接下来forEach是一个以功能为输入的功能...

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

...并且对于中的每个元素thisthis类似数组的地方,都有一个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>顺序,都在调用提供的函数。

编辑

逻辑结论/清除

在下面,有一个指向文章的链接,该文章建议我们每次都放弃对函数式编程的尝试,并坚持每次手动,内联循环,因为这种解决方案有些hacker且难看。

我想说的是,虽然.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);

现在,如果您自己问自己是否需要执行此操作,答案很可能不是……
这确实是通过... ... 如今这些具有高级功能的Every(?)库完成的。
如果您使用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 ];

超级干净,非常有用。
查找剩余价差运算符;在BabelJS网站上试用它们;如果您的技术堆栈正常,请在Babel的生产环境中使用它们并进行构建。


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


现在我有点困惑,原因是:console.log(this); 我将始终获得window对象,并且我认为.call会更改此值
Muhammad Saleh 2015年

.call 确实会更改的值this,这就是为什么要call与forEach一起使用的原因。如果forEach看起来像,forEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }则通过this说出forEach.call( newArrLike )意思进行更改,它将使用该新对象作为this
Norguard 2015年

2
@MuhammadSaleh .call不会改变的价值this的传递给里面有什么forEach.call改变的值this 的foreach内。如果您对为什么this不将其传递给forEach想要调用的函数感到困惑,则应查找thisJavaScript中的行为。作为附录,只适用在这里(和地图/过滤/减少),有一个第二个参数forEach,这组this函数内arr.forEach(fn, thisInFn)[].forEach.call(arrLike, fn, thisInFn);或刚使用.bind; arr.forEach(fn.bind(thisInFn));
Norguard

1
@thetrystero就是重点。[]只是作为数组存在,因此您可以使用.call.forEach方法,并将其更改为this要使用的集合。.call看起来是这样的:function (thisArg, a, b, c) { var method = this; method(a, b, c); }除了有内部黑魔法设置this里面method等于你为通过thisArg
Norguard

1
简而言之... Array.from()?
zakius19年

53

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

由于a NodeList与数组相似,因此数组方法实际上可以在其上工作,因此通过使用,[].forEach.call您可以Array.prototype.forEach在的上下文中调用该方法NodeList,就好像您可以简单地做到yourNodeList.forEach(/*...*/)

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

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

7
因此,需要澄清的是,仅在尝试遍历没有自己原型的类似数组的结构时,在具有标准数组的日常应用程序中使用[].forEach.call(['a','b'], cb)over 就没有优势了吗?那是对的吗?['a','b'].forEach(cb)forEach
马特·弗莱彻

2
@MattFletcher:是的,这是正确的。两者都可以工作,但是为什么事情变得过于复杂呢?只需直接在数组本身上调用该方法即可。
James Allardice 2015年

太好了,谢谢。而且我不知道为什么,也许只是为了街头信誉,给ALDI等老妇留下深刻的印象。我会坚持[].forEach():)
马特·弗莱彻

var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) 效果很好
daslicht

@daslicht不在旧版IE中!(但是确实支持forEach阵列,但不支持NodeList对象)
Djave

21

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

这是一个很好的示例代码,为了简单和清楚起见,应对其进行重构。与其使用[].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
});

5

最好用

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

});

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


3
[].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
});

2

想更新这个老问题:

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

NodeList对象是节点的集合,通常由诸如Node.childNodes之类的属性以及诸如document.querySelectorAll()之类的方法返回。

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

但是,某些较旧的浏览器尚未实现NodeList.forEach()或Array.from()。可以使用Array.prototype.forEach()来规避此问题-请参阅本文档的示例。


1

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

您可以在此处找到该call功能的文档。对于文档是在这里forEach


1

只需添加一行:

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

瞧!

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

请享用 :-)

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


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

0

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

调用具有给定值和单独提供的参数的函数。dn


[]实际上将始终返回一个数组,同时window.Array可以和任何被覆盖(在所有的旧版浏览器),所以也可以window.Array.prototype.forEach被覆盖任何东西。在不支持getter / setter或对象密封/冻结(冻结会导致其自身的可扩展性问题)的浏览器中,两种情况均不能保证安全。
Norguard

随时编辑答案以添加有关覆盖prototype或方法的可能性的额外信息:forEach
Xotic750 2015年

0

Norguard解释什么 [].forEach.call()不和詹姆斯·阿勒代斯 为什么我们这样做:因为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);
        }
    };
}

0

此页面上有很多很好的信息(请参阅答案 + 答案 + 评论),但是我最近遇到了与OP相同的问题,并且花了一些时间才能了解整个情况。所以,这是一个简短的版本:

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

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

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

使用更新的模式(在ECMAScript 2015之后)Array.from()

Array.from(document.querySelectorAll('a')).forEach(a => {})
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.