JavaScript 5中for和foreach的区别

3

我了解到,for 循环遍历数组的每个元素,即使指定位置不存在,而 forEach 则跳过不存在的位置。

此外,在编写 for 循环时,必须指定计数器(我不是在说 for...in),而 forEach 只使用函数迭代器。

好的。请回答以下四个问题:

  1. 真的吗?forEach 跳过不存在的数组位置,而 for 不会跳过吗?
  2. forEach 是否使用内部计数器(我们只需不写它)?还是与 for 完全不同的机制?
  3. 在某些语言中,forEach 不按照数组的顺序遍历数组。在 JavaScript 中呢?
  4. 最后,除了语法/句法之外,它们还有其他区别吗?

2
阅读文档可以回答你大部分的问题。forEach ... for - charlietfl
3个回答

3

因此,当您谈论for循环时,您指的是实际的for循环,而不是for...in(不应在数组上使用)。似乎存在一些误解,让我从这个角度开始:

for循环不是用于循环遍历数组的。并不是说你不应该或者它不好,但那不是它的主要功能 - 它只是循环直到满足条件然后停止。最常见的语法恰好是for (var i =0; i < max; i++),它很适用于数组循环,但这并不意味着您不能使用其他的语法,例如for (var i = 100; Number.isInteger(i); i = i/2)。因此,针对您的问题,以下是答案:

1. forEach是否跳过数组中缺失的项?

答案:是的

forEach是一个数组方法,与mapreduce等一起,它确实跳过未分配的插槽。这有助于处理稀疏数组,例如

var arr = [];
arr[5] = "five";
arr[7] = "seven";
arr[10] = "ten";

console.log("---using forEach---");
arr.forEach(function(item) {
  console.log(item);
});

console.log("---using a for-loop---");
for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

这是一个简单的例子,说明使用稀疏数组循环的原因 - 正如您所看到的,您可以获得更少的代码执行。这里还有一个稍微复杂一些的例子:

var arr = [];
arr[5] = "five";
arr[42] = "the answer";
arr[9001] = "over nine thousand";

arr.forEach(function(item) {
  console.log(item);
});

当你只有三个项目时,一个简单的for循环会执行(字面上)超过九千次。

我必须在这里澄清一些事情-未设置值是你没有分配任何内容的索引。分配undefined仍然算作分配:

var arr = [];
arr[5] = "five";
arr[7] = undefined;
arr[10] = "ten";

arr.forEach(function(item) {
  console.log(item);
});

2. forEach方法是否使用内部计数器?

答案:是的

你可以很容易地获取当前结果,因为它会被传递到回调函数中。

var arr = [];
arr[5] = "five";
arr[42] = "the answer";
arr[9001] = "over nine thousand";

arr.forEach(function(item, index) {
  console.log(index, "-", item);
});

3. forEach 的执行顺序是什么

答案:从零开始,按升序执行

以上内容应该已经向您展示了这个问题的答案。以下是另一个快速示例:

var alphabet = ["a", "b", "c", "d", "e", "f", "g" ];

alphabet.forEach(function(letter, index) {
  console.log(index, letter);
})

4. forforEach之间的区别

答案: 很难进行比较

两者之间的区别正如我在开始时所提到的 - for是JavaScript中的通用循环运算符 - 你可以使用它来遍历数组,但也可以用于各种其他目的。相比之下,forEach专门绑定在数组上。实际上很难进行真正的比较,但如果我们仅仅限制为对其在数组上的使用,那么以下是一些要点:

for

  • + 你可以使用 breakreturn 提前退出循环,因此不需要遍历整个数组
  • + 你可以控制数组的遍历,因此可以从末尾开始并向下计数 (for (var i = arr.length; i >= 0; i--)),或者甚至可以回退或跳过项 (在循环体内执行 i--i += 2)
  • + 因为在许多其他语言中都使用,所以被广泛认可作为循环机制
  • +/- 即使对于稀疏数组,你也将遍历整个数组长度。
  • - 即使你不关心它,也必须维护循环计数器。

forEach

  • + 能够很好地处理稀疏数组
  • + 接受回调函数,因此可以通过将它们传递到.forEach调用中来重用函数
  • + 上述的另一个好处是回调体内所有内容的功能作用域(尽管使用ES6的letconst时这些作用得到了减少)
  • + 习惯用法JavaScript
  • +/- 对于稀疏数组,只会遍历存在的项
  • - 不能提前退出循环,尽管你可能会使用find
  • - 你无法控制数组的遍历方式。

PS: for...of也值得一提

感谢 @Bergi,我记得我确实想在这里包含一些内容:

你没有提到的一个循环是for...of循环,它的语法与for...in循环相似,但它被设计用于处理数组以及其他可迭代对象。这是一个相对较新的构造(至少比forEach要新),因为它在ES6规范中,所以广泛的网络支持可能不会有,但它是上述两个的替代方案。使用起来非常简单:

var arr = [];
arr[5] = "five";
arr[7] = "seven";
arr[10] = "ten";

for (item of arr) {
  console.log(item);
}

所以,简单来说,它的工作方式类似于 for 循环,因为它会遍历所有内容,但您不必声明和维护计数器。

无论如何,关于forEach如何处理稀疏数组的问题存在争议,许多人希望得到undefined值而不是跳过。 - Bergi
我认为forfor...of.forEach更符合惯用语。 - Bergi
@Bergi 关于稀疏数组,这可能取决于您想如何处理它们。根据我的经验,大多数情况下,我只需要其中存在的内容,尽管我会说这并不是所有时间。我肯定可以看到循环遍历所有内容的用途。然而,似乎JavaScript(或者可能是ECMAScript)已经采取了稀疏数组应该仅包含其现有成员中的任何一个的立场。因此,“好”可能需要在那里进行限定,但对于某些用例,它是合适的。 - VLAZ
@Bergi 关于 for 循环,它在任何地方都是惯用语(可能应该这样表达,而不是“广为认可”),但我不认为它是 JS 特有的。像 forEachmapreducefilterfind 这样的数组方法更具有独特的 JavaScript 特色。当然,它们也存在于其他语言中,但在谈论 JS 数组处理机制时,它们值得一提,并且因此更加突出和广为人知。 - VLAZ

3
for关键字是一种语言结构,是迭代事物的最快且最基础的方式。它有三种形式:
  1. C风格的forfor (初始化; 条件; 迭代器 ) { 代码... } - 这是最灵活和历经时间考验的版本。要迭代列表,您需要从0到列表长度遍历所有列表索引。您还可以遍历每个其他、第三个等元素。大多数情况下,这已经足够了。

  2. Javascript的for-infor (var key in object) { code ... } - 这是遍历对象中每个键的好方法,例如,输出JSON对象的所有值。

  3. (ES2015) Javascript的for-of: for (var item of collection) { code ... } - 这是在现代浏览器中新添加的功能。它允许您跳过索引和计数器,并让您遍历集合中的每个项目。例如,产品列表中的每个对象。它执行与C样式for相同的操作,但使用起来更容易。

然而,forEach函数仅适用于Javascript中Array对象,并使您能够为数组中的每个项目运行一个函数。如果您有一个执行所有工作的实用程序函数,那么这很好。

以下是如何使用上述所有迭代类型:

// most plain kind of array with a length of 7
var myArray = [1, 2, 3, 4, 5, 6, undefined];
// a weird kind of array with no values between index 3 and 100
// its length is 102 
var mySparseArray = [1, 2, 3, 4];
mySparseArray[100] = 5;
mySparseArray[101] = 6;

// 1. C-style for
// ** can control how index is incremented
// ** needs an extra variable to iterate with
for (var i = 0; i < myArray.length; i += 1) {
    console.log(myArray[i]);
    // logs 1, 2, 3, 4, 5, 6, undefined
}

for (var i = 0; i < mySparseArray.length; i += 1) {
    console.log(myArray[i]); 
    // logs 1, 2, 3, 4, undefined, undefined, ... (up to index 100), 5, 6
} 

// 2. for..in
// ** keys are iterated over in non-guaranteed order
//    (you might get 2, "length", 1, 0, 3)
// ** all enumerable keys are included, that might include things other than indexes. 
for (var key in myArray) {
    console.log(myArray[key]);
    // logs 1, 2, 3, 4, 5, 6 
}

for (var key in mySparseArray) {
    console.log(mySparseArray[key]);
    // logs 1, 2, 3, 4, 5, 6
    // this for-loop "thinks" the array is an object with numbers for keys
}

// 3. for..of
// ** only available in browsers with ES2015 support
// ** supports many other things than Arrays - TypedArrays, Iterators...
for (var item of myArray) {
    console.log(item);
}

for (var item of mySparseArray) {
    console.log(item);
    // logs 1, 2, 3, 4, undefined, undefined, ... (up to index 100), 5, 6
}

// 4. forEach
// ** calls a function for each element (considered slow)
// ** supports only Arrays (unless you call it with Array.prototype.forEach.call)
function myCallback(element) {
  console.log(element);
}
myArray.forEach(myCallback); // logs 1, 2, 3, 4, 5, 6
mySparseArray.forEach(myCallback); // logs 1, 2, 3, 4, 5, 6

请务必在浏览器的开发者控制台中尝试这些代码!您可以查看上述每个代码片段的实际效果。

请务必查看迭代的MDN参考资料。


关于跳过,OP特指不存在的属性,而不仅仅是值为undefined的属性。.forEach确实会跳过它们,可以尝试使用稀疏数组。 - Bergi
length不应该是可枚举的,所以它不会被for...in迭代。 - Oriol

1

vlaz已经在评论中回答了,我只想补充一下:

forEach调用的主要区别在于它可以为数组中的每个元素生成一个闭包,这在需要重复使用某些内部变量时非常有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接