概述
问题描述:
如何使用 JavaScript 遍历数组中的所有条目?
解决方案1:
huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求
TL;博士
您最好的选择通常是 for-of 循环(仅限 ES2015+;规范 | MDN)- 简单且异步友好 for (const element of theArray) { // …use element
… } forEach(仅限 ES5+; spec | MDN)(或它的亲戚) - 不是异步友好的(但请参阅详细信息) theArray.forEach(element => { // …use element
… });一个简单的老式 for 循环 - 异步友好 for (let index = 0; index < theArray.length; ++index) { const element = theArray[index]; // … 使用 element
… } (很少) for-in 和安全措施 - 异步友好 for (const propertyName in theArray) { if (/…is an array element property (见下文)。 …/) { 常量元素 = theArray[propertyName]; // …使用元素
… } }
for-of 循环(仅限 ES2015+;规范 | MDN)- 简单且异步友好的 for (const element of theArray) { // …使用 element
… }
forEach(仅限 ES5+;规范 | MDN)(或其亲属等) - 不是异步友好的(但请参阅详细信息) theArray.forEach(element => { // …use element
… });
一个简单的老式 for 循环 - 异步友好 for (let index = 0; index < theArray.length; ++index) { const element = theArray[index]; // …使用元素
… }
(很少)带有保护措施的 for-in - 异步友好 for (const propertyName in theArray) { if (/…is an array element property (see below)…/) { const element = theArray[propertyName] ; // …使用元素
… } }
一些快速的“不要”:不要使用 for-in,除非你使用它有安全措施,或者至少知道它为什么会咬你。如果您不使用它的返回值,请不要使用 map。 (可悲的是,有人教 map [spec / MDN],就好像它是 forEach 一样——但正如我在我的博客上写的那样,这不是它的用途。如果你不使用它创建的数组,请不要使用 map。 ) 如果回调执行异步工作并且您希望 forEach 等到该工作完成(因为它不会),请不要使用 forEach。
不要使用 for-in ,除非你使用它有安全措施,或者至少知道它为什么会咬你。
如果您不使用它的返回值,请不要使用 map。 (可悲的是,有人在教 map [spec / MDN],就好像它是 forEach 一样——但正如我在博客上写的那样,这不是它的用途。如果你不使用它创建的数组,请不要使用 map。 )
如果回调执行异步工作并且您希望 forEach 等到该工作完成(因为它不会),请不要使用 forEach。
但是还有更多需要探索,请继续阅读…
JavaScript 具有强大的语义,用于循环遍历数组和类似数组的对象。我将答案分为两部分:真正数组的选项,以及只是数组的选项 -like,例如 arguments 对象、其他可迭代对象 (ES2015+)、DOM 集合, 等等。
好的,让我们看看我们的选择:
对于实际数组
您有五个选项(两个基本上永远支持,另一个由 ECMAScript 5 [“ES5”] 添加,另外两个在 ECMAScript 2015 中添加(“ES2015”,又名"ES6"):
使用 for-of(隐式使用迭代器)(ES2015+) 使用 forEach 和相关(ES5+) 使用简单的 for 循环 正确使用 for-in 显式使用迭代器(ES2015+)
(您可以在此处查看这些旧规范:ES5、ES2015,但两者都已被取代;当前编辑的草稿始终为 here。)
细节:
1.使用for-of(隐式使用迭代器)(ES2015+)
ES2015 将 iterators and iterables 添加到 JavaScript。数组是可迭代的(字符串、Map 和 Set 以及 DOM 集合和列表也是如此,稍后您将看到)。可迭代对象为其值提供迭代器。新的 for-of 语句循环遍历迭代器返回的值:
常量 a = [“a”, “b”, “c”]; for (const element of a) { // 你可以使用 let
代替 const
如果你喜欢 console.log(element); } // a // b // c
没有比这更简单的了!在幕后,它从数组中获取一个迭代器并循环遍历迭代器返回的值。数组提供的迭代器按开始到结束的顺序提供数组元素的值。
注意 element 是如何作用于每个循环迭代的;在循环结束后尝试使用 element 会失败,因为它不存在于循环体之外。
理论上,for-of 循环涉及多个函数调用(一个用于获取迭代器,然后一个用于从中获取每个值)。即使这是真的,也没什么好担心的,函数调用在现代 JavaScript 引擎中非常很便宜(它困扰了我 forEach [下文],直到我研究它; details)。但此外,在处理诸如数组之类的本机迭代器时,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);控制台.log(消息); } } showSlowly([ “So”, “long”, “and”, “thanks”, “for”, “all”, “the”, “fish!” ]); // .catch
被省略,因为我们知道它从不拒绝
请注意单词在每个单词之前出现的延迟。
这是一个编码风格的问题,但是当循环任何可迭代的东西时,for-of 是我首先想到的。
2.使用forEach及相关
在您可以访问 ES5 添加的 Array 功能的任何甚至模糊的现代环境(不是 IE8)中,如果您只处理同步,则可以使用 forEach (spec | MDN)代码(或者您不需要在循环期间等待异步进程完成):
常量 a = [“a”, “b”, “c”]; a.forEach((element) => { console.log(element); });
forEach 接受一个回调函数,并且可以选择在调用该回调时用作 this 的值(上面未使用)。为数组中的每个元素调用回调,按顺序跳过稀疏数组中不存在的元素。虽然我在上面只使用了一个参数,但调用回调时使用了三个参数:该迭代的元素、该元素的索引以及对您正在迭代的数组的引用(如果您的函数还没有它)便利)。
与 for-of 一样,forEach 的优点是您不必在包含范围内声明索引和值变量;在这种情况下,它们作为参数提供给迭代函数,并且非常适合该迭代。
与 for-of 不同,forEach 的缺点是它不理解 async 函数和 await。如果您使用 async 函数作为回调,则 forEach不会等待该函数的承诺在继续之前完成。这是 for-of 中使用 forEach 的 async 示例 - 请注意初始延迟是如何出现的,但随后所有文本立即出现而不是等待:
function delay(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); } async function showSlowly(messages) { // 错误,在继续之前不等待, // 不处理承诺拒绝 messages.forEach(async message => { await delay(400); console.log(message); } ); } showSlowly([ “So”, “long”, “and”, “thanks”, “for”, “all”, “the”, “fish!” ]); // .catch
被省略,因为我们知道它从不拒绝
forEach 是“循环遍历它们”函数,但 ES5 定义了其他几个有用的“遍历数组并执行操作”函数,包括:
every (spec | MDN) - 在回调第一次返回虚假值时停止循环
some (spec | MDN) - 在回调第一次返回真值时停止循环
filter (spec | MDN) - 创建一个新数组,包括回调返回真实值的元素,省略不返回真实值的元素
map (spec | MDN) - 从回调返回的值创建一个新数组
reduce (spec | MDN) - 通过重复调用回调建立一个值,传入以前的值;有关详细信息,请参阅规范
reduceRight (spec | MDN) - 类似于 reduce,但按降序而不是升序工作
与 forEach 一样,如果您使用 async 函数作为回调,则这些函数都不会等待函数的承诺解决。这意味着:
对every、some 和filter 使用异步函数回调是不合适的,因为它们会将返回的promise 视为真实值;他们不等待承诺解决,然后使用履行价值。
使用异步函数回调通常适用于 map,如果目标是将数组转换为 promise 数组,可能用于传递给 promise 组合函数之一(Promise.all、Promise.race、promise.allSettled、或 Promise.any)。
在 reduce 或 reduceRight 中使用异步函数回调很少合适,因为(再次)回调将始终返回一个 Promise。但是有一个习惯用法是从一个使用reduce的数组构建一个promise链(const promise = array.reduce((p, element) => p.then(/…something using element
… /))????,但通常在这些情况下,异步函数中的 for-of 或 for 循环会更清晰,更易于调试。
3.使用简单的for循环
有时老方法是最好的:
常量 a = [“a”, “b”, “c”]; for (let index = 0; index < a.length; ++index) { const element = a[index];控制台.log(元素); }
如果数组的长度在循环期间不会改变,并且它在对性能高度敏感的代码中,那么预先获取长度的稍微复杂一点的版本可能会快一点:
常量 a = [“a”, “b”, “c”]; for (let index = 0, len = a.length; index < len; ++index) { const element = a[index];控制台.log(元素); }
和/或倒数:
常量 a = [“a”, “b”, “c”]; for (let index = a.length - 1; index >= 0; --index) { const element = a[index];控制台.log(元素); }
但是对于现代 JavaScript 引擎,您很少需要勉强挤出最后一点汁液。
在 ES2015 之前,循环变量必须存在于包含范围内,因为 var 只有函数级范围,没有块级范围。但正如您在上面的示例中看到的,您可以在 for 中使用 let 将变量范围限定为循环。当您这样做时,会为每次循环迭代重新创建 index 变量,这意味着在循环体中创建的闭包会为该特定迭代保留对 index 的引用,这解决了旧的“循环中的闭包”问题:
// (querySelectorAll
中的 NodeList
类似于数组) const divs = document.querySelectorAll(“div”); for (let index = 0; index < divs.length; ++index) { divs[index].addEventListener(‘click’, e => { console.log("Index is: " + index); }); } 零 一 二 三 四
在上面,如果单击第一个,则会得到“索引为:0”,如果单击最后一个,则会得到“索引为:4”。如果您使用 var 而不是 let,这不起作用(您总是会看到“索引为:5”)。
与 for-of 一样,for 循环在 async 函数中运行良好。这是前面使用 for 循环的示例:
function delay(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); } 异步函数 showSlowly(messages) { for (let i = 0; i < messages.length; ++i) { const message = messages[i];等待延迟(400);控制台.log(消息); } } showSlowly([ “So”, “long”, “and”, “thanks”, “for”, “all”, “the”, “fish!” ]); // .catch
被省略,因为我们知道它从不拒绝
4.正确使用for-in
for-in 不是用于遍历数组,而是用于遍历对象属性的名称。作为数组是对象这一事实的副产品,它似乎经常用于循环遍历数组,但它不仅循环遍历数组索引,它还循环遍历数组的 all 可枚举属性对象(包括继承的对象)。 (以前也是不指定顺序的,现在是【详情见this other answer】,但现在虽然指定了顺序,但规则复杂,也有例外,不依赖顺序最佳实践。)
数组上 for-in 的唯一实际用例是:
它是一个稀疏数组,其中有大量间隙,或者
您在数组对象上使用非元素属性,并且希望将它们包含在循环中
只看第一个示例:如果您使用适当的保护措施,您可以使用 for-in 访问那些稀疏数组元素:
// a
是一个稀疏数组 const a = [];一[0] =“一”; a[10] = “b”; a[10000] = “c”; for (const name in a) { if (Object.hasOwn(a, name) && // 这些检查是 /^0KaTeX parse error: Undefined control sequence: d at position 8: |^[1-9]̲d̲*/.test(name) && // 解释名称<= 4294967294 // 下面 ) { const element = a[name]; console.log(a[name]); } }
注意三个检查:
对象有它自己的属性(不是从它的原型继承的;这个检查也经常写为 a.hasOwnProperty(name) 但 ES2022 添加了 Object.hasOwn 可以更可靠),并且名称是所有十进制数字(例如,正常的字符串形式,而不是科学记数法),并且当强制转换为数字时,名称的值是 <= 2^32 - 2(即 4,294,967,294)。这个数字是从哪里来的?它是规范中数组索引定义的一部分。其他数字(非整数、负数、大于 2^32 - 2 的数字)不是数组索引。它是 2^32 - 2 的原因是这使得最大索引值比 2^32 - 1 小一,这是数组长度可以具有的最大值。 (例如,数组的长度适合 32 位无符号整数。)
…尽管话虽如此,但大多数代码只进行 hasOwnProperty 检查。
当然,您不会在内联代码中这样做。你会写一个实用函数。也许:
// 没有 forEach
的陈旧环境的实用函数 const hasOwn = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);常量 rexNum = /^0KaTeX parse error: Undefined control sequence: d at position 8: |^[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); } } } 常量 a = []; a[5] = “五”; a[10] = “十”; a[100000] = “十万”; ab = “蜜蜂”; sparseEach(a, (value, index) => { console.log(“” + index + “处的值是” + 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)) { // 几乎总是这是人们做的唯一检查 const message = messages[name];等待延迟(400);控制台.log(消息); } } } showSlowly([ “So”, “long”, “and”, “thanks”, “for”, “all”, “the”, “fish!” ]); // .catch
被省略,因为我们知道它从不拒绝
- 显式使用迭代器 (ES2015+)
for-of 隐式使用迭代器,为您完成所有工作。有时,您可能希望显式使用迭代器。它看起来像这样:
常量 a = [“a”, “b”, “c”]; const it = a.values(); // 或者 const it = a[Symbol.iterator]();
如果你喜欢 let entry; while (!(entry = it.next()).done) { const element = entry.value;控制台.log(元素); }
迭代器是与规范中的迭代器定义匹配的对象。每次调用时,它的 next 方法都会返回一个新的 result 对象。结果对象有一个属性 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);常量元素 = entry.value;控制台.log(元素); } } showSlowly([ “So”, “long”, “and”, “thanks”, “for”, “all”, “the”, “fish!” ]); // .catch
被省略,因为我们知道它从不拒绝
对于类数组对象
除了真正的数组之外,还有 类数组 对象具有 length 属性和全数字名称的属性:NodeList instances、HTMLCollection instances、arguments 对象等。我们如何遍历它们的内容?
使用上面的大多数选项
上述数组方法中的至少一些,可能是大多数甚至全部都同样适用于类似数组的对象:
使用 for-of(隐式使用迭代器)(ES2015+)for-of 使用对象提供的迭代器(如果有)。这包括主机提供的对象(如 DOM 集合和列表)。例如,来自 getElementsByXYZ 方法的 HTMLCollection 实例和来自 querySelectorAll 的 NodeLists 实例都支持迭代。 (这是由 HTML 和 DOM 规范非常巧妙地定义的。基本上,任何具有长度和索引访问的对象都是自动可迭代的。它不必标记为可迭代;它仅用于除了可迭代之外的集合,支持 forEach、values、keys 和 entries 方法。NodeList 支持;HTMLCollection 不支持,但两者都是可迭代的。)这是一个遍历 div 元素的示例:
常量 divs = document.querySelectorAll(“div”); for (const div of divs) { div.textContent = Math.random(); } 零 一 二 三 四
使用 forEach 和相关 (ES5+) Array.prototype 上的各种函数是“有意通用的”,可以通过 Function#call (spec | MDN) 或 Function#apply (spec | MDN) 用于类似数组的对象。 (如果您必须处理 IE8 或更早版本 [哎哟],请参阅此答案末尾的“主机提供的对象的警告”,但这不是模糊现代浏览器的问题。)假设您想在上使用 forEach Node 的 childNodes 集合(作为 HTMLCollection,本身没有 forEach)。你可以这样做: Array.prototype.forEach.call(node.childNodes, (child) => { // 用 child
做一些事情 }); (但请注意,您可以只在 node.childNodes 上使用 for-of。)如果您要经常这样做,您可能希望将函数引用的副本抓取到变量中以供重用,例如:/ / (这可能都在一个模块或一些作用域函数中) const forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); // 然后稍后… forEach(node.childNodes, (child) => { // 对 child
做一些事情 });使用简单的 for 循环 很明显,简单的 for 循环适用于类似数组的对象。显式使用迭代器(ES2015+)参见#1。
您也许能够摆脱 for-in(使用安全措施),但有了所有这些更合适的选项,就没有理由尝试了。
创建一个真正的数组
其他时候,您可能希望将类似数组的对象转换为真正的数组。这样做非常容易:
使用 Array.from Array.from (spec) | (MDN)(ES2015+,但很容易填充)从类似数组的对象创建一个数组,可选地首先通过映射函数传递条目。所以: const divs = Array.from(document.querySelectorAll(“div”)); …从 querySelectorAll 中获取 NodeList 并从中创建一个数组。如果您要以某种方式映射内容,映射功能很方便。例如,如果您想获取具有给定类的元素的标签名称数组: // 典型用法(带有箭头函数): const divs = Array.from(document.querySelectorAll(“.some-class” ), 元素 => element.tagName); // 传统函数(因为 Array.from
可以填充): var divs = Array.from(document.querySelectorAll(“.some-class”), function(element) { return element.tagName; });使用扩展语法 (…) 也可以使用 ES2015 的扩展语法。与 for-of 一样,它使用对象提供的迭代器(参见上一节中的 #1): const trueArray = […iterableObject];因此,例如,如果我们想将 NodeList 转换为真正的数组,使用扩展语法这变得非常简洁: const divs = […document.querySelectorAll(“div”)];使用数组的 slice 方法我们可以使用数组的 slice 方法,它和上面提到的其他方法一样是“故意通用的”,因此可以用于类数组对象,例如:const trueArray = Array.prototype.slice。调用(arrayLikeObject);例如,如果我们想将 NodeList 转换为真正的数组,我们可以这样做: const divs = Array.prototype.slice.call(document.querySelectorAll(“div”)); (如果您仍然必须处理 IE8 [哎哟],将会失败;IE8 不允许您像这样使用主机提供的对象。)
主机提供的对象的警告
如果您将 Array.prototype 函数与 host-provided 类数组对象(例如,由浏览器而非 JavaScript 引擎提供的 DOM 集合等)一起使用,则 IE8 等过时的浏览器不一定能处理这样,如果您必须支持它们,请务必在您的目标环境中进行测试。但这不是现代浏览器的问题。 (对于非浏览器环境,自然要根据环境而定。)
@Alex - 数组上不代表数组元素的属性。例如:const a = ["a", "b"]; a.example = 42; 该数组具有三个属性(除了所有数组都具有的属性),其名称为字符串 "0"、"1" 和 "example"。名为 "example" 的属性是一个非元素属性。另外两个是元素属性,因为它们代表数组的元素。
@PeterKionga-Kamau - 这不是一个关联数组,它是一个对象。您在 var arr = new Array(); 中创建的数组被丢弃并由您在 arr = {"test":"testval", "test2":"test2val"}; 中创建的对象替换。该代码应该只是 var arr = {"test":"testval", "test2":"test2val"};(嗯,不是 var,而是 let 或 const)。当然,根据某些定义,对象可能被认为是关联数组,但在其他定义中,它们不是,我避免在 JS 中使用该术语,因为它在 PHP 中具有特定含义,这是 JavaScript 相邻的,因为它们都在 Web 工作中被大量使用.
@PeterKionga-Kamau - 问题和答案是关于数组,而不是(其他)对象。但是:对象属性没有索引,所以对象没有索引访问;相反,它们具有键控访问权限(theObject.propName、theObject["propName"]、theObject[propKeySymbol] 等)。索引访问的概念对对象没有用处。不过,这是非常间接的可能。 ???? 对象属性现在确实有顺序(ES2015+,在后来的几个规范中进行了调整),但是顺序很复杂,它取决于属性创建的顺序,属性键的类型,...
...属性键的值(!)如果它是一个字符串,以及该属性是继承的还是“拥有的”,因此依赖属性顺序是不好的做法。如果无论如何都想这样做,没有一个操作可以按顺序提供所有属性键,但 Reflect.ownKeys 会按顺序提供对象的 own 属性键数组(跳过继承的属性键)。因此,如果这适合用例,您可以从中获取一个数组 (const keys = Reflect.ownKeys(theObject);)。 ...
...然后“索引”访问将索引到该键的数组中,然后使用键从对象中获取值:theObject[keys[index]]。不过,我无法想象它的用例。如果您想要对象的所有属性(继承的 + 自己的),那就更复杂了,您必须循环通过原型链附加您还没有看到的属性(Set 在那里可能很有用,因为 Set 是严格的按值插入排序并且不允许重复):...
解决方案2:
huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。
注意:这个答案已经过时了。如需更现代的方法,请查看 the methods available on an array。感兴趣的方法可能是:
为每个
地图
筛选
压缩
减少
每一个
一些
在 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。
在 ECMAScript 5 中,数组原型上会有一个 forEach 方法,但旧版浏览器不支持它。因此,为了能够始终如一地使用它,您必须有一个支持它的环境(例如,用于服务器端 JavaScript 的 Node.js),或者使用“Polyfill”。然而,这个功能的 Polyfill 是微不足道的,因为它使代码更容易阅读,所以它是一个很好的 polyfill。
有没有办法在一行代码中做到这一点。例如,在 facebook 中,我喜欢使用 document.getElementsByTagName("video")[28].playbackRate = 2.2 加速视频。如果我可以轻松地映射所有元素,那么我就可以避免识别哪个视频(例如,在这种情况下为索引 28)。有任何想法吗?
@stevec: Array.from(document.querySelectorAll('video')).forEach(video => video.playbackRate = 2.2);
解决方案3:
HuntsBot周刊–不定时分享成功产品案例,学习他们如何成功建立自己的副业–huntsbot.com
如果您使用 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].
}
解决方案4:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
向后循环
我认为反向 for 循环值得在这里提及:
for (var i = array.length; i--; ) {
// process array[i]
}
优点:
您不需要声明一个临时 len 变量,或者在每次迭代时与 array.length 进行比较,其中任何一个都可能是一个微小的优化。
以相反的顺序从 DOM 中删除兄弟姐妹通常更有效。 (浏览器需要对其内部数组中的元素进行较少的移动。)
如果您在循环时在索引 i 处或之后修改数组(例如,您在数组 [i] 处删除或插入一个项目),那么前向循环将跳过向左移动到位置 i 的项目,或重新处理第 i 个向右移动的项目。在传统的 for 循环中,您可以更新 i 以指向下一个需要处理的项目 - 1,但简单地反转迭代方向通常是一种更简单、更优雅的解决方案。
同样,在修改或删除嵌套的 DOM 元素时,反向处理可以规避错误。例如,考虑在处理其子节点之前修改父节点的 innerHTML。到达子节点时,它将与 DOM 分离,并在编写父级的 innerHTML 时被新创建的子级替换。
与其他一些可用选项相比,它的键入和阅读时间更短。虽然它输给了 forEach() 和 ES6 的 for … of。
缺点:
它以相反的顺序处理项目。如果您要根据结果构建一个新数组,或者在屏幕上打印内容,那么输出自然会与原始顺序相反。
重复地将兄弟姐妹作为第一个孩子插入 DOM 以保持它们的顺序效率较低。 (浏览器将不得不不断地改变事情。)为了有效和有序地创建 DOM 节点,只需像往常一样循环向前和追加(并且还使用“文档片段”)。
反向循环让初级开发人员感到困惑。 (您可能会认为这是一种优势,具体取决于您的观点。)
我应该一直使用它吗?
一些开发人员默认使用反向 for 循环,除非有充分的理由向前循环。
尽管性能提升通常微不足道,但它有点尖叫:
“只要对列表中的每一项都这样做,我不在乎顺序!”
然而在实践中,这不是实际上是一个可靠的意图指示,因为它与您确实关心订单的那些场合没有区别,并且需要反向循环。因此,实际上需要另一个构造来准确表达“不关心”意图,这是目前大多数语言(包括 ECMAScript)中不可用的,但可以称为 forEachUnordered()。
如果顺序无关紧要,并且效率是一个问题(在游戏或动画引擎的最内层循环中),那么使用反向 for 循环作为您的首选模式可能是可以接受的。请记住,在现有代码中看到反向 for 循环并不一定意味着顺序无关!
最好使用 forEach()
一般来说,对于清晰性和安全性更受关注的高级代码,我之前建议使用 Array::forEach 作为循环的默认模式(尽管这些天我更喜欢使用 for…of)。选择 forEach 而不是反向循环的原因是:
读起来更清楚。
它表明 i 不会在块内移动(这总是隐藏在 long for 和 while 循环中的可能意外)。
它为您提供了一个免费的闭包范围。
它减少了局部变量的泄漏和与外部变量的意外碰撞(和突变)。
然后,当您确实在代码中看到反向 for 循环时,这暗示它被反转是有充分理由的(可能是上述原因之一)。看到传统的前向 for 循环可能表明可以发生转移。
(如果对意图的讨论对您没有意义,那么您和您的代码可能会从观看 Crockford 关于 Programming Style & Your Brain 的讲座中受益。)
现在使用 for…of 效果更好!
关于 for…of 或 forEach() 是否更可取存在争议:
为了获得最大的浏览器支持,for…of 需要一个用于迭代器的 polyfill,这会使您的应用程序执行速度稍慢,下载速度稍大。
出于这个原因(并鼓励使用 map 和 filter),一些前端风格指南完全禁止 for…of!
但是上述问题不适用于 Node.js 应用程序,现在 for…of 得到了很好的支持。
此外,等待在 forEach() 中不起作用。在这种情况下,使用 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 处的项目,这避免了 Array-out-of-bounds 未定义项目的任何问题。
为什么它不会在索引 0 之前停止迭代?当条件 i-- 计算为假值(当它产生 0 时)时,循环将停止迭代。诀窍在于,与–i 不同,尾随的i-- 运算符递减i,但产生递减之前的值。你的控制台可以证明这一点: > var i = 5; [我,我–,我]; [5, 5, 4] 所以在最后一次迭代中,i 之前是 1,而 i-- 表达式将其更改为 0,但实际上产生 1(真实),因此条件通过。在下一次迭代中 i-- 将 i 更改为 -1 但产生 0(错误),导致执行立即退出循环底部。在传统的前向 for 循环中,i++ 和 ++i 是可以互换的(正如 Douglas Crockford 指出的那样)。但是在反向 for 循环中,因为我们的减量也是我们的条件表达式,所以我们必须坚持使用 i-- 如果我们想处理索引 0 处的项目。
琐事
有些人喜欢在反向 for 循环中画一个小箭头,并以眨眼结束:
for (var i = array.length; i --> 0 ;) {
感谢 WYL 向我展示了反向 for 循环的好处和恐惧。
解决方案5:
huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求
一些 C 风格的语言使用 foreach 循环枚举。在 JavaScript 中,这是通过 for…in loop structure 完成的:
var index,
value;
for (index in obj) {
value = obj[index];
}
有一个问题。 for…in 将遍历对象的每个可枚举成员及其原型上的成员。为了避免读取通过对象原型继承的值,只需检查属性是否属于对象:
for (i in obj) {
if (obj.hasOwnProperty(i)) {
//do stuff
}
}
此外,ECMAScript 5 已向 Array.prototype 添加了一个 forEach 方法,该方法可用于使用回调枚举数组(polyfill 在文档中,因此您仍然可以将它用于旧版浏览器):
arr.forEach(function (val, index, theArray) {
//do stuff
});
请务必注意,当回调返回 false 时,Array.prototype.forEach 不会中断。 jQuery 和 Underscore.js 在 each 上提供了它们自己的变体,以提供可以短路的循环。
解决方案6:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
对于…的 |为每个 |地图
使用现代 JavaScript 语法遍历数组
const fruits = ['????', '????', '????' ]
???????? 为…的
for (const fruit of fruits) {
console.log(fruit)
// '????', '????', '????'
}
???????? 为每个
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
????, ????, ????,
解决方案7:
huntsbot.com洞察每一个产品背后的需求与收益,从而捕获灵感
如果要循环遍历数组,请使用标准的三部分 for 循环。
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
您可以通过缓存 myArray.length 或向后迭代它来获得一些性能优化。
解决方案8:
huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式
如果您不介意清空数组:
var x;
while(x = y.pop()){
alert(x); //do something
}
x 将包含 y 的最后一个值,并将从数组中删除。您还可以使用 shift(),它会从 y 中提供和删除第一项。
解决方案9:
一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会
forEach 实现 (see in 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
}
);
解决方案10:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
我知道这是一个旧帖子,并且已经有很多很棒的答案。为了更完整一点,我想我会使用 AngularJS 添加另一个。当然,这只适用于你使用 Angular 的情况,很明显,尽管如此我还是想把它说出来。
angular.forEach 接受 2 个参数和一个可选的第三个参数。第一个参数是要迭代的对象(数组),第二个参数是迭代器函数,可选的第三个参数是对象上下文(在循环内部基本上称为“this”。
有不同的方法可以使用 angular 的 forEach 循环。最简单也可能最常用的是
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 循环。现在,我将在 cons 部分讨论效率,因为 forEach 循环比 for 循环慢得多。我作为专业人士提到这一点是因为保持一致和标准化很好。
考虑以下 2 个嵌套循环,它们的作用完全相同。假设我们有 2 个对象数组,每个对象包含一个结果数组,每个结果都有一个 Value 属性,它是一个字符串(或其他)。假设我们需要遍历每个结果,如果它们相等,则执行一些操作:
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。
解决方案11:
huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求
从 ECMAScript 6 开始:
list = [0, 1, 2, 3] for (let obj of list) { console.log(obj) }
其中 of 避免了与 in 相关的奇怪之处,并使其像任何其他语言的 for 循环一样工作,并且 let 将 i 绑定在循环内,而不是在函数内。
当只有一个命令时(例如在上面的示例中),可以省略大括号 ({})。
原文链接:https://www.huntsbot.com/qa/qX2y/for-each-over-an-array-in-javascript?lang=zh_CN
huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。
最后
以上就是优雅缘分为你收集整理的在 JavaScript 中对数组进行 for-each问题描述:解决方案1:解决方案2:解决方案3:解决方案4:解决方案5:解决方案6:解决方案7:解决方案8:解决方案9:解决方案10:解决方案11:的全部内容,希望文章能够帮你解决在 JavaScript 中对数组进行 for-each问题描述:解决方案1:解决方案2:解决方案3:解决方案4:解决方案5:解决方案6:解决方案7:解决方案8:解决方案9:解决方案10:解决方案11:所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复