如何使用JavaScript循环遍历数组中的所有条目?
如何使用JavaScript循环遍历数组中的所有条目?
简而言之
Your best bets are usually
for-of
loop (ES2015+ only; spec | MDN) - simple and async
-friendly
for (const element of theArray) {
// ...use `element`...
}
forEach
(ES5+ only; spec | MDN) (or its relatives some
and such) - not async
-friendly (but see details)
theArray.forEach(element => {
// ...use `element`...
});
for
loop - async
-friendly
for (let index = 0; index < theArray.length; ++index) {
const element = theArray[index];
// ...use `element`...
}
for-in
with safeguards - async
-friendly
for (const propertyName in theArray) {
if (/*...is an array element property (see below)...*/) {
const element = theArray[propertyName];
// ...use `element`...
}
}
Some quick "don't"s:
for-in
unless you use it with safeguards or are at least aware of why it might bite you.map
if you're not using its return value.map
[spec / MDN] as though it were forEach
— but as I write on my blog, that's not what it's for. If you aren't using the array it creates, don't use map
.)forEach
if the callback does asynchronous work and you want the forEach
to wait until that work is done (because it won't).但探索的内容还有很多,继续阅读...
JavaScript 具有强大的语义,可以循环遍历数组和类数组对象。我将答案分为两部分:对于真正的数组的选项,以及对于只是类数组但不是真正数组的东西(例如 arguments
对象、其他可迭代对象(ES2015+)、DOM 集合等)的选项。
好的,让我们看看我们的选择:
您有五个选项(两个基本上一直得到支持,另一个由 ECMAScript 5(“ES5”)添加,另外两个在 ECMAScript 2015(“ES2015”,也称为“ES6”)中添加):
for-of
(隐式使用迭代器)(ES2015+)forEach
和相关方法(ES5+)for
循环for-in
(可以在这里查看旧规范:ES5,ES2015,但两者都已被取代;当前的编辑草案始终在 这里。)
详细信息:
for-of
(隐式使用迭代器)(ES2015+)ES2015 将 迭代器和可迭代对象 添加到了 JavaScript 中。数组是可迭代的(字符串、Map
和 Set
也是可迭代的,以及后面将要看到的 DOM 集合和列表)。可迭代对象为其值提供迭代器。新的 for-of
语句循环遍历迭代器返回的值:
const a = ["a", "b", "c"];
for (const element of a) { // You can use `let` instead of `const` if you like
console.log(element);
}
// a
// b
// c
这真的再简单不过了!在底层,它从数组中获取一个迭代器并循环遍历迭代器返回的值。由数组提供的迭代器按顺序提供数组元素的值。
注意如何将element
限定在每个循环迭代中;在循环结束后尝试使用element
将会失败,因为它不存在于循环体外。
理论上,for-of
循环涉及多个函数调用(一个用于获取迭代器,然后一个用于从中获取每个值)。即使如此,在现代JavaScript引擎中,函数调用非常廉价(对于forEach
[下面]让我感到困扰,直到我研究了一下;详情)。但是,JavaScript引擎还会优化掉这些调用(在处理像数组这样的本机迭代器时)。
for-of
完全支持async
。如果需要在循环体中完成的工作是按顺序(而不是并行)完成的,则循环体中的await
将等待承诺解决后继续执行。以下是一个傻瓜示例:
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function showSlowly(messages) {
for (const message of messages) {
await delay(400);
console.log(message);
}
}
showSlowly([
"So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch` omitted because we know it never rejects
for-of
。forEach
等方法Array
功能,如果您仅处理同步代码(或者在循环过程中不需要等待异步进程完成),则可以使用forEach
(规范 | MDN)。
const a = ["a", "b", "c"];
a.forEach((element) => {
console.log(element);
});
forEach
接受一个回调函数和一个可选的值,用作在调用回调函数时使用的this
(上面未使用)。回调函数按顺序为数组中的每个元素调用,在稀疏数组中跳过不存在的元素。虽然上面只使用了一个参数,但回调函数会传递三个参数:该迭代的元素,该元素的索引以及对您正在迭代的数组的引用(以防您的函数没有方便地使用它)。
与for-of
类似,forEach
的优点是您不必在包含范围中声明索引和值变量;在这种情况下,它们作为迭代函数的参数提供,因此仅限于该迭代的范围内。
与for-of
不同,forEach
的缺点在于它不理解async
函数和await
。如果您将async
函数用作回调,则forEach
在继续之前不会等待该函数的承诺解决。这是使用forEach
而不是for-of
的async
示例-请注意,有一个初始延迟,但然后所有文本立即出现而不是等待:
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function showSlowly(messages) {
// INCORRECT, doesn't wait before continuing,
// doesn't handle promise rejections
messages.forEach(async message => {
await delay(400);
console.log(message);
});
}
showSlowly([
"So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch` omitted because we know it never rejects
forEach
是一个循环遍历数组的函数,但是ES5定义了其他几个有用的“通过数组并执行操作”函数,包括:
every
(规范 | MDN) - 当回调返回假值时停止循环some
(规范 | MDN) - 当回调返回真值时停止循环filter
(规范 | MDN) - 创建一个新数组,其中包括回调返回真值的元素,省略不返回真值的元素map
(规范 | MDN) - 根据回调返回的值创建一个新数组reduce
(规范 | MDN) - 通过重复调用回调并传递先前的值来构建一个值;详细信息请参见规范reduceRight
(规范 | MDN) - 类似于reduce
,但是按降序而不是升序工作与forEach
一样,如果您将异步函数用作回调,则这些函数都不会等待函数的承诺解决。这意味着:
async
函数回调来处理every
、some
和filter
, 因为它们会将返回的promise视为真值;它们不会等待promise被解决并使用兑现的值。Promise.all
、Promise.race
、promise.allSettled
或Promise.any
),则通常需要使用map
和async
函数回调。async
函数回调来处理reduce
或reduceRight
, 因为(再一次)回调函数总是返回一个promise。但一种从数组构建promise链的惯用语法使用reduce
,(const promise = array.reduce((p, element) => p.then(/*...something using `element`...*/));
),但通常在这种情况下,在async
函数中使用for-of
或for
循环会更清晰易于调试。for
循环有时候旧方法是最好的方法:
const a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
const element = a[index];
console.log(element);
}
如果在循环期间数组长度不会改变,并且它在高性能敏感代码中,那么一个稍微复杂一点的版本可以提前获取长度,这可能会略微快一点:
const a = ["a", "b", "c"];
for (let index = 0, len = a.length; index < len; ++index) {
const element = a[index];
console.log(element);
}
const a = ["a", "b", "c"];
for (let index = a.length - 1; index >= 0; --index) {
const element = a[index];
console.log(element);
}
但是在现代JavaScript引擎中,你很少需要挤出最后一点性能。
在ES2015之前,循环变量必须存在于包含作用域中,因为var
只有函数级别的作用域,而不是块级别的作用域。但正如你在上面的示例中看到的那样,你可以在for
内部使用let
来将变量限定在循环中。当你这样做时,index
变量会在每个循环迭代中重新创建,这意味着在循环体中创建的闭包会保留对该特定迭代的index
的引用,从而解决了旧的“循环中的闭包”问题:
// (The `NodeList` from `querySelectorAll` is array-like)
const divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
var
而不是let
,这个方法就不会起作用(你总是会看到“索引是:5”)。for-of
一样,在async
函数中,for
循环也能很好地工作。下面是之前的例子,使用for
循环实现:
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function showSlowly(messages) {
for (let i = 0; i < messages.length; ++i) {
const message = messages[i];
await delay(400);
console.log(message);
}
}
showSlowly([
"So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch` omitted because we know it never rejects
for-in
for-in
不适用于循环数组,而是用于循环对象属性的名称。由于数组是对象,因此它通常似乎可以用于循环数组,但它不仅仅循环数组索引,它循环对象的所有可枚举属性(包括继承的属性)。 (过去它的顺序没有指定; 现在已经指定了[详见此其他答案],但即使现在规则已经指定,规则也很复杂,有例外,并且依赖于顺序并不是最佳实践。)
for-in
在数组上的唯一真正用例是:
仅看第一个示例:如果您使用适当的保护措施,可以使用 for-in
访问这些稀疏数组元素:
// `a` is a sparse array
const a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (const name in a) {
if (Object.hasOwn(a, name) && // These checks are
/^0$|^[1-9]\d*$/.test(name) && // explained
name <= 4294967294 // below
) {
const element = a[name];
console.log(a[name]);
}
}
a.hasOwnProperty(name)
,但ES2022添加了Object.hasOwn
,这可能更可靠),以及length
可以达到的最大值。(例如,一个数组的长度适合32位无符号整数。)hasOwnProperty
检查。// Utility function for antiquated environments without `forEach`
const hasOwn = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);
const rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
for (const name in array) {
const index = +name;
if (hasOwn(a, name) &&
rexNum.test(name) &&
index <= 4294967294
) {
callback.call(thisArg, array[name], index, array);
}
}
}
const a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";
sparseEach(a, (value, index) => {
console.log("Value at " + index + " is " + value);
});
像 for
循环一样,如果要在异步函数中按顺序执行某些操作,for-in
循环也可以很好地实现。
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function showSlowly(messages) {
for (const name in messages) {
if (messages.hasOwnProperty(name)) { // Almost always this is the only check people do
const message = messages[name];
await delay(400);
console.log(message);
}
}
}
showSlowly([
"So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch` omitted because we know it never rejects
for-of
隐式地使用迭代器,为您完成所有的工作。有时候,您可能想要 显式地 使用迭代器。它看起来像这样:
const a = ["a", "b", "c"];
const it = a.values(); // Or `const it = a[Symbol.iterator]();` if you like
let entry;
while (!(entry = it.next()).done) {
const element = entry.value;
console.log(element);
}
迭代器是一个符合规范中Iterator定义的对象。它的next
方法在每次调用时返回一个新的结果对象。结果对象具有一个属性done
告诉我们它是否完成,还有一个可选的属性value
表示该迭代的值(如果这个属性为false
,那么done
是可选的;如果这个属性为undefined
,那么value
是可选的)。
value
的值取决于迭代器的类型。在数组上,缺省的迭代器会提供每个数组元素的值(例如在之前的例子中是"a"
、"b"
和"c"
)。数组还有另外三个方法可以返回迭代器:
values()
:这是[Symbol.iterator]
方法的别名,返回缺省的迭代器。keys()
:返回一个迭代器,该迭代器提供数组中的每个键(索引),在上面的例子中,它将提供"0"
,然后是"1"
,最后是"2"
(是字符串形式)。entries()
:返回一个迭代器,该迭代器提供[key, value]
数组。由于迭代器对象直到调用next
方法才会前进,因此它们在async
函数循环中表现良好。下面是使用显式的迭代器来重新编写之前的for-of
示例:
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function showSlowly(messages) {
const it = messages.values()
while (!(entry = it.next()).done) {
await delay(400);
const element = entry.value;
console.log(element);
}
}
showSlowly([
"So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch` omitted because we know it never rejects
除了真正的数组,还有一些具有类数组特性的对象,它们具有length
属性和以数字为名称的属性,例如NodeList
实例、HTMLCollection
实例、arguments
对象等。如何遍历它们的内容呢?
至少有一种,可能是大部分或甚至全部的数组方法同样适用于类数组对象:
使用 for-of
(隐式使用迭代器) (ES2015+)
for-of
使用对象提供的迭代器(如果有)。这包括由主机提供的对象(如 DOM 集合和列表)。例如,从 getElementsByXYZ
方法返回的HTMLCollection
实例和从 querySelectorAll
返回的NodeList
实例都支持迭代。(这在 HTML 和 DOM 规范中定义得非常微妙。基本上,任何具有length
和索引访问的对象都自动可迭代。它不需要被标记为iterable
;这只用于支持forEach
、values
、keys
和entries
方法的集合。 NodeList
是可迭代的,而HTMLCollection
不是,但两者都是可迭代的)。
下面是遍历div
元素的示例:
const divs = document.querySelectorAll("div");
for (const div of divs) {
div.textContent = Math.random();
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
Use forEach
and related (ES5+)
The various functions on Array.prototype
are "intentionally generic" and can be used on array-like objects via Function#call
(spec | MDN) or Function#apply
(spec | MDN). (If you have to deal with IE8 or earlier [ouch], see the "Caveat for host-provided objects" at the end of this answer, but it's not an issue with vaguely-modern browsers.)
Suppose you wanted to use forEach
on a Node
's childNodes
collection (which, being an HTMLCollection
, doesn't have forEach
natively). You'd do this:
Array.prototype.forEach.call(node.childNodes, (child) => {
// Do something with `child`
});
(Note, though, that you could just use for-of
on node.childNodes
.)
If you're going to do that a lot, you might want to grab a copy of the function reference into a variable for reuse, e.g.:
// (This is all presumably in a module or some scoping function)
const forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
// Then later...
forEach(node.childNodes, (child) => {
// Do something with `child`
});
Use a simple for
loop
Perhaps obviously, a simple for
loop works for array-like objects.
Use an iterator explicitly (ES2015+)
See #1.
你也许可以使用for-in
(加上保护措施)来解决问题,但是有更适当的选项可供选择,因此不必尝试。
有时候,你可能想将一个类似数组的对象转换为真正的数组。这样做非常简单:
Use Array.from
Array.from
(spec) | (MDN) (ES2015+, but easily polyfilled) creates an array from an array-like object, optionally passing the entries through a mapping function first. So:
const divs = Array.from(document.querySelectorAll("div"));
...takes the NodeList
from querySelectorAll
and makes an array from it.
The mapping function is handy if you were going to map the contents in some way. For instance, if you wanted to get an array of the tag names of the elements with a given class:
// Typical use (with an arrow function):
const divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Traditional function (since `Array.from` can be polyfilled):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Use spread syntax (...
)
It's also possible to use ES2015's spread syntax. Like for-of
, this uses the iterator provided by the object (see #1 in the previous section):
const trueArray = [...iterableObject];
So for instance, if we want to convert a NodeList
into a true array, with spread syntax this becomes quite succinct:
const divs = [...document.querySelectorAll("div")];
Use the slice
method of arrays
We can use the slice
method of arrays, which like the other methods mentioned above is "intentionally generic" and so can be used with array-like objects, like this:
const trueArray = Array.prototype.slice.call(arrayLikeObject);
So for instance, if we want to convert a NodeList
into a true array, we could do this:
const divs = Array.prototype.slice.call(document.querySelectorAll("div"));
(If you still have to handle IE8 [ouch], will fail; IE8 didn't let you use host-provided objects as this
like that.)
如果您使用Array.prototype
函数与主机提供的类似数组的对象(例如,由浏览器提供而不是JavaScript引擎提供的DOM集合等),过时的浏览器(如IE8)可能无法正确处理,所以如果您需要支持这些浏览器,请务必在目标环境中进行测试。但对于现代浏览器来说,这不是问题。(对于非浏览器环境,自然会取决于环境。)
const a = ["a", "b"]; a.example = 42;
这个数组有三个属性(除了所有数组都有的属性),它们的名称是字符串 "0"
、"1"
和 "example"
。名为 "example"
的属性是非元素属性。其他两个是元素属性,因为它们表示数组的元素。 - T.J. Crowdervar arr = new Array();
中创建的数组被抛弃并被你在 arr = {"test":"testval", "test2":"test2val"};
中创建的对象所取代。这段代码应该只写成 var arr = {"test":"testval", "test2":"test2val"};
(好吧,不是 var
而是 let
或 const
)。尽管按照某些定义,对象可能被认为是关联数组,但按照其他定义,它们不是,并且我在 JavaScript 中避免使用这个术语,因为它在 PHP 中有特定的含义,PHP 与 JavaScript 都在 Web 工作中广泛使用。 - T.J. CrowdertheObject.propName
、theObject["propName"]
、theObject[propKeySymbol]
等)。对于对象来说,索引访问的概念并不有用。虽然非常间接地实现是可能的。对象属性现在有一个顺序(ES2015+,在几个后续规范中进行了一些调整),但这个顺序很复杂,它取决于属性创建的顺序、属性键的类型,... - T.J. CrowderReflect.ownKeys
按顺序提供对象自有属性键的数组(跳过继承的属性)。因此,如果适用于用例,则可以从中获取数组(const keys = Reflect.ownKeys(theObject);
)。... - T.J. Crowderlet keys = []; for (let obj = theObject; obj; obj = Object.getPrototypeOf(obj)) { keys.push(...Reflect.ownKeys(obj)); } keys = [...new Set(keys)];
这段代码的作用是,获取对象及其原型链上所有可枚举的属性名,并去重后返回一个数组。这是因为在 JavaScript 中,如果一个对象拥有多个原型,那么它的属性查找顺序是先查找自己的属性,再查找第一个原型的属性,然后是第一个原型的原型的属性,以此类推。因此我们需要按照这个顺序逐层遍历对象和其原型,将所有可枚举的属性名存储在一个数组中,最后使用 Set 去重并返回结果数组。如果一个属性同时存在于对象和它的原型中,则只保留对象自身的属性名。 - T.J. Crowder注意:此答案已经过时。要了解更现代的方法,请查看数组可用的方法。感兴趣的方法可能包括:
在JavaScript中迭代数组的标准方法是使用原始的for
循环:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
for .. in
循环可能是更好的选择。然而,您必须使用适当的保护措施来确保仅对数组的所需属性(即数组元素)进行操作,因为for..in
循环也将在旧版浏览器中枚举,或者如果其他属性被定义为enumerable
。document.getElementsByTagName("video")[28].playbackRate = 2.2
。如果我能够轻松地遍历所有元素,那么我就可以避免识别哪个视频(例如在这种情况下是索引28)。有什么想法吗? - stevec如果你正在使用 jQuery 库,你可以使用 jQuery.each 方法:
$.each(yourArray, function(index, value) {
// do your stuff here
});
编辑:
根据问题,用户希望使用JavaScript代替jQuery编写代码,因此修改如下:
var length = yourArray.length;
for (var i = 0; i < length; i++) {
// Do something with yourArray[i].
}
我认为这里应该提到反向循环:
for (var i = array.length; i--; ) {
// process array[i]
}
len
变量,也不需要在每次迭代中与array.length
进行比较,这两种方法都可能是微小的优化。array[i]
),那么正向循环将跳过向左移动到位置i的项,或重新处理向右移动的第i个项。在传统的for循环中,你可以更新i以指向下一个需要处理的项-1,但简单地反转迭代方向通常是一个更加简单和更优雅的解决方案。forEach()
和ES6的for ... of
。一些开发人员默认使用反向循环,除非有充分的理由需要正向循环。
虽然性能增益通常微不足道,但这似乎在喊:
"对列表中的每个项目都这样做,我不在乎顺序!"
但是,在实践中,这实际上并不能可靠地表明意图,因为它与那些你在意顺序的场合以及确实需要反向循环的场合无法区别。因此,实际上需要另一个结构来准确地表示"不在乎"的意图,这在大多数语言,包括ECMAScript中当前不可用,但可以称之为forEachUnordered()
。
如果顺序不重要,并且效率是一个问题(例如在游戏或动画引擎的最内层循环中),那么使用反向循环作为您的首选模式可能是可以接受的。只需记住,在现有代码中看到反向for循环并不一定意味着顺序不重要!
Array::forEach
作为循环的默认模式(尽管现在我更喜欢使用for..of
)。 选择forEach
而不是反向循环的原因如下:
for
和while
循环中的惊喜)。然后,当您在代码中看到反向循环时,这表明它被反转有一个很好的理由(也许是上述原因之一)。而看到传统的正向循环可能表明移位可以发生。
如果“意图”的讨论对您来说毫无意义,那么您和您的代码可能会受益于观看Crockford关于编程风格和大脑的演讲。
关于for..of
和forEach()
哪个更好的争论还在继续:
为了获得最大的浏览器支持,for..of
需要一个迭代器的polyfill,这会使你的应用程序执行稍微变慢,下载稍微变大。
因此(并且为了鼓励使用map
和filter
),一些前端样式指南完全禁止使用for..of
!
但是以上问题不适用于Node.js应用程序,在那里for..of
现在得到了很好的支持。
个人而言,我倾向于使用看起来最容易阅读的内容,除非性能或缩小文件大小是一个重大问题。因此,现在我更喜欢使用for..of
而不是forEach()
,但在适用时我仍然会使用map
或filter
或find
或some
。(出于同事的考虑,我很少使用reduce
.)
for (var i = 0; i < array.length; i++) { ... } // Forwards
for (var i = array.length; i--; ) { ... } // Reverse
i--
是中间子句(通常我们在那里看到比较),而最后一个子句是空的(通常我们在那里看到i++
)。这意味着i--
也被用作继续的条件。关键是,在每次迭代之前执行和检查它。
如何在不溢出的情况下从array.length
开始?
因为i--
在每次迭代之前运行,所以在第一次迭代中,我们实际上将访问array.length-1
处的项目,这避免了任何与数组越界 undefined
项相关的问题。
为什么它不会在索引0之前停止迭代?
当条件i--
计算出一个falsy值(当它产生0时)时,循环将停止迭代。
诀窍在于,与--i
不同,尾随的i--
操作符会递减i
但产生在递减之前的值。在控制台上可以演示这一点:
> var i = 5; [i, i--, i];
[5, 5, 4]
因此,在最后一次迭代中,i先前为1,而i--
表达式将其更改为0,但实际上产生了1(真),因此条件通过。在下一次迭代中,i--
将i更改为-1,但产生了0(假),导致执行立即退出循环底部。
在传统的正向for循环中,i++
和++i
是可互换的(如Douglas Crockford所指出的)。然而,在反向for循环中,因为我们的递减也是我们的条件表达式,如果我们想处理索引0处的项目,我们必须坚持使用i--
。
有些人喜欢在反向for
循环中绘制一个小箭头,并以一个眨眼结束:
for (var i = array.length; i --> 0 ;) {
感谢WYL向我展示了反向循环的好处和可怕之处。
一些 C 风格的语言使用 foreach
来循环枚举。在 JavaScript 中,可以使用 for..in
循环结构实现:
var index,
value;
for (index in obj) {
value = obj[index];
}
有个需要注意的地方。 for..in
循环会遍历对象本身和原型链上所有可枚举的属性。为了避免读取从对象原型中继承而来的属性值,只需检查属性是否属于对象即可:
for (i in obj) {
if (obj.hasOwnProperty(i)) {
//do stuff
}
}
另外,ECMAScript 5 添加了一个forEach
方法到Array.prototype
,可以使用回调函数来枚举数组(文档中提供了 polyfill,因此您仍然可以使用它来支持旧的浏览器):
arr.forEach(function (val, index, theArray) {
//do stuff
});
需要注意的是,Array.prototype.forEach
在回调函数返回false
时并不会停止循环。 jQuery和Underscore.js提供了它们自己的each
变体,可以提供可短路的循环。
const fruits = ['', '', '' ]
for...of
的翻译如下:for...of
for (const fruit of fruits) {
console.log(fruit) // '', '', ''
}
forEach
fruits.forEach(fruit => {
console.log(fruit) // '', '', ''
})
映射函数
*与前两者不同,map() 创建一个新的数组并期望在每次迭代后您返回一些内容。
fruits.map(fruit => fruit) // ['', '', '' ]
重要提示: map() 方法旨在在每次迭代中返回一个值,它是转换数组元素的理想方法:
fruits.map(fruit => 'cool ' + fruit) // ['cool ', 'cool ', 'cool ' ]
另一方面,for...of和forEach( )不需要返回任何值,因此我们通常使用它们执行逻辑任务来操纵外部的操作。
也就是说,在这两个函数中,你会发现 if() 语句、副作用和日志记录活动。
提示:在 .map() 或 .forEach() 中,你还可以在每次迭代中获取索引(以及整个数组)。只需向它们传递附加参数即可:
fruits.map((fruit, i) => i + ' ' + fruit)
// ['0 ', '1 ', '2 ' ]
fruits.forEach((f, i, arr) => {
console.log( f + ' ' + i + ' ' + arr )
})
// 0 , , ,
// 1 , , ,
// 2 , , ,
如果你想循环遍历一个数组,可以使用标准的三部分 for
循环。
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
通过缓存myArray.length
或者反向迭代可以获得一些性能优化。
如果您不介意清空数组:
var x;
while(x = y.pop()){
alert(x); //do something
}
x
将包含 y
的最后一个值,并且该值将从数组中被移除。你也可以使用 shift()
方法从 y
中获取并移除第一个元素。
angular.forEach
需要两个参数和一个可选的第三个参数。第一个参数是要迭代的对象(数组),第二个参数是迭代器函数,可选的第三个参数是对象上下文(在循环内部称为“this”)。var temp = [1, 2, 3];
angular.forEach(temp, function(item) {
//item will be each element in the array
//do something
});
var temp = [1, 2, 3];
var temp2 = [];
angular.forEach(temp, function(item) {
this.push(item); //"this" refers to the array passed into the optional third parameter so, in this case, temp2.
}, temp2);
虽然如此,你不必这样做,你可以简单地执行以下操作,它等同于先前的示例:
angular.forEach(temp, function(item) {
temp2.push(item);
});
angular.forEach
函数和内置的原生for
循环有优缺点。
优点:
angular.forEach
将使用ES5 forEach循环。现在,在缺点部分,我们将讨论forEach循环比for循环慢得多的效率问题。我之所以将这个作为优点提到,是因为保持一致和标准化很好。考虑以下两个嵌套循环,它们完全相同。假设我们有两个对象数组,每个对象包含一个结果数组,每个结果都有一个值属性(或其他)。假设我们需要迭代每个结果,如果它们相等,则执行某些操作:
angular.forEach(obj1.results, function(result1) {
angular.forEach(obj2.results, function(result2) {
if (result1.Value === result2.Value) {
//do something
}
});
});
//exact same with a for loop
for (var i = 0; i < obj1.results.length; i++) {
for (var j = 0; j < obj2.results.length; j++) {
if (obj1.results[i].Value === obj2.results[j].Value) {
//do something
}
}
}
虽然这只是一个非常简单的假设性例子,但我已经使用第二种方法编写了三重嵌套的for循环,阅读和编写都非常困难。
缺点
angular.forEach
还是原生的forEach
,都比普通的for
循环慢得多...大约90%的速度。因此对于大型数据集,最好坚持使用原生的for
循环。continue
,要在angular.forEach
中继续,只需在函数中放置一个return;
语句,如angular.forEach(array, function(item) { if (someConditionIsTrue) return; });
,这将导致它在该迭代中退出函数。这也是因为原生的forEach
也不支持break或continue。我相信还有其他的优缺点,如果您认为有必要,请随意添加。总之,如果您需要效率,请坚持使用本地的for
循环来满足您的循环需求。但是,如果您的数据集较小,并且可以牺牲一些效率以换取可读性和可写性,那么请在其中添加一个angular.forEach
。
一个 forEach 实现(在 jsFiddle 中查看):
function forEach(list,callback) {
var length = list.length;
for (var n = 0; n < length; n++) {
callback.call(list[n]);
}
}
var myArray = ['hello','world'];
forEach(
myArray,
function(){
alert(this); // do something
}
);