我正在查看一些代码片段,发现有多个元素通过应用于一个空数组的forEach来调用一个函数来遍历节点列表。
例如,我有如下代码:
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
但我不理解它是如何工作的。有人能解释一下在forEach
前面的空数组的行为以及call
是如何工作的吗?
我正在查看一些代码片段,发现有多个元素通过应用于一个空数组的forEach来调用一个函数来遍历节点列表。
例如,我有如下代码:
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
但我不理解它是如何工作的。有人能解释一下在forEach
前面的空数组的行为以及call
是如何工作的吗?
[]
是一个数组。
这个数组完全没有被使用。
它被添加到页面上,因为使用数组可以访问数组原型,例如.forEach
。
这比输入Array.prototype.forEach.call(...);
更快捷。
接下来,forEach
是一个以函数作为输入的函数...
[1,2,3].forEach(function (num) { console.log(num); });
...并且对于this
中的每个元素(其中this
是类似数组的,具有length
属性,可以像this[1]
一样访问其部分),它会传递三个内容:
2
)最后,.call
是函数的原型(它是一个在其他函数上调用的函数)。
.call
将使用其第一个参数替换正常函数内部的this
,作为第一个参数传递给call
的任何值(在日常JS中,undefined
或null
将使用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>
调用提供的函数。
下面有一篇文章的链接,建议我们放弃尝试使用函数式编程,每次都坚持手动、内联循环,因为这个解决方案是hack-ish和难看的。
我认为虽然.forEach
不如它的对应项.map(transformer)
、.filter(predicate)
、.reduce(combiner, initialValue)
有用,但当你真正想要做的只是修改外部世界(而不是数组),n次,同时访问arr[i]
或i
时,它仍然有其作用。
那么我们如何处理这种差异,因为Motto显然是一个有才华和知识渊博的人,而我想象自己知道我在做什么/我在哪里去(现在和然后... ...其他时候是头脑风暴学习)?
答案实际上非常简单,是Uncle Bob和Sir Crockford都会因为疏忽而面露尴尬的:
清理它。
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);
};
为了让那些想要像使用数组一样使用列表的人们(正如他们应该做的那样)更方便,slice( )
/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 ];
超级干净,非常实用。
查找Rest和Spread运算符; 在BabelJS网站上尝试它们; 如果您的技术栈有序,请在使用Babel和构建步骤时将其用于生产。
没有充分的理由不能将非数组转换为数组的转换...只是不要在代码中做什么都是复制相同的丑陋行。
querySelectorAll
方法 返回一个类似于数组的 NodeList
对象,但它并不是一个真正的数组。 因此,它没有 forEach
方法(该方法是由数组对象通过 Array.prototype
继承而来)。NodeList
类似于数组,因此数组方法实际上可以在其上运行,因此通过使用 [].forEach.call
,您正在在上下文中调用 NodeList
的 Array.prototype.forEach
方法,就好像您能够简单地执行yourNodeList.forEach(/*...*/)
一样。Array.prototype.forEach.call(/*...*/);
[].forEach.call(['a','b'], cb)
与 ['a','b'].forEach(cb)
相比没有优势,只有在尝试迭代没有其自己的原型上具有 forEach
方法的类似数组结构时才有作用?这样说对吗? - Matt Fletcher[].forEach()
:) - Matt Fletcher section.forEach((item)=>{
console.log(item.offsetTop)
})```
代码运行良好。
- daslichtforEach
,但不支持NodeList对象) - Djave[].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
});
[].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
});
可以使用更好的方式进行编写
Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {
});
它所做的是document.querySelectorAll('a')
返回一个类似于数组的对象,但它不继承自Array
类型。
因此,我们使用Array.prototype
对象调用forEach
方法,并将上下文设置为由document.querySelectorAll('a')
返回的值。
需要更新关于旧问题的信息:
在现代浏览器中,使用document.querySelectorAll("a").foreach()
直接循环遍历元素的原因已经越来越少了。
NodeList对象是节点的集合,通常由诸如Node.childNodes之类的属性和document.querySelectorAll()之类的方法返回。
虽然NodeList不是Array,但是可以使用forEach()迭代它。也可以使用Array.from()将其转换为真正的数组。
但是,一些旧的浏览器没有实现NodeList.forEach()或Array.from()。这可以通过使用Array.prototype.forEach()来规避-请参阅本文档的示例。
NodeList
上使用Array
方法,而该节点列表本身没有这些方法。Function.call()
占用了数组的方法,并使用了一个数组字面量([]
)而不是Array.prototype
,因为它打字更短。[].forEach.call(document.querySelectorAll('a'), a => {})
Array.from()
:Array.from(document.querySelectorAll('a')).forEach(a => {})
我总是用一个快速而简单的解决方案。我不会去修改原型,这是一种好的实践。当然,有很多方法可以让它更好,但你已经明白了。
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}`);
});
Just add one line:
NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
就这样!
document.querySelectorAll('a').forEach(function(el) {
// whatever with the current node
});
享受:—)
警告:NodeList是全局类。如果您正在编写公共库,请不要使用此建议。但是,当您在网站或node.js应用程序上工作时,这是增加自我效能的非常方便的方式。
.call
确实会改变this
的值,这就是为什么你要在forEach中使用call
的原因。如果forEach看起来像forEach(fn){var i = 0; var len = this.length; for (; i <length; i + = 1) {fn(this [i],i,this); }
,那么通过使用forEach.call(newArrLike)
来改变this
意味着它将使用该新对象作为this
。 - Norguard.call
不会改变你传入forEach
的函数内部的this
值,但是它会改变forEach
函数内部的this
值。如果你对于为什么this
值不能从forEach
传递到想要调用的函数感到困惑,你应该查找 JavaScript 中this
的行为。另外,在这里(还有 map/filter/reduce),forEach
还有第二个参数,用来设置函数内部的this
值:arr.forEach(fn, thisInFn)
或者[].forEach.call(arrLike, fn, thisInFn);
。你也可以直接使用.bind
:arr.forEach(fn.bind(thisInFn));
。 - Norguard[]
只是作为一个数组存在,这样你就可以.call
.forEach
方法,并将this
更改为你要处理的集合。.call
看起来像这样:function (thisArg, a, b, c) { var method = this; method(a, b, c); }
除了在method
内部设置this
等于你传递的thisArg
的内部黑魔法。 - Norguard[].forEach.call(arr, fn)
将运行arr
的长度,传递到call
中;而不是在forEach
开始时使用的数组(如前两句所述,并在此之后详细说明)。初始数组的长度完全无关紧要。当您使用forEach
函数的第二个参数时,您正在使用索引;与直接将函数传递给forEach
而不是call
一样。如果记录第一个参数,您将获得"a"、"b"、"c"
,而不是1、2、3
,这是由于call
如何替换this
和forEach
的工作方式所致。 - Norguard