回答

收藏

在 JavaScript 中的数组上的 For-each

技术问答 技术问答 324 人阅读 | 0 人回复 | 2023-09-11

如何使用 JavaScript 遍历数组的所有条目?$ R/ j; M, ^  s$ u  m6 ]9 w
我以为是这样的:2 ~, H$ }% {! O/ u: H2 S. Q; ^
    forEach(instance in theArray)6 v% c) l, Q. P
theArray我的数组在哪里,但这似乎是不正确的。6 V4 W, V: b% i6 J
                                                                + @2 X% H- O+ q! }3 o$ q, n3 y' r
    解决方案:                                                                * Q* [3 R; x9 N, E
                                                                TL; DR
1 J& M$ q) @9 w6 N  L. N' v; P你最好通常是4 A0 S& c  Q, `" C& e1 o9 O
一个for-of循环(限 ES2015 ;规范| MDN)——简单且async友好
" l6 h5 L  o, b$ F7 S# Rfor (const element of theArray)       ...use `element`...  }
' H$ {+ L: Q/ HforEach(ES5  只;规格| MDN)(或其亲属some和这样) -    async-友好(见详情)
; j7 w# e9 b4 o+ u+ ?: }& i$ stheArray.forEach(element =>        ...use `element`...   };
8 x/ O" Y" }: P4 S! b  A' P简单的老式for循环——async友好. a$ O! y# C" Y% \& V8 A, a$ I
for (let index = 0; index
6 i  |& C$ b* ^$ |$ t5 z2 c- v2 H(很少)    for-in 有保护措施- -async友好
) S+ k3 c5 `5 C* Vfor (const propertyName in theArray)    if (/*...is an array element property (see below)...*/)        const element = theArray[propertyName];     ...use `element`...   一些快速的不:7 ?1 B" ^$ g0 H( Y1 r1 {0 K
不要使用,for-in除非你使用它有安全措施,或者至少知道它为什么会咬你。0 h6 x, k& ]1 {( f* G
不要使用map,如果你不使用它的返回值
+ S( P6 \7 w3 V8 U1 E- y(可悲的是,有人在那里教书。map[ spec / MDN 似乎是这样forEach -但正如我在我的博客上写的,这不是它的用途。如果您不使用它创建的数组,请不要使用它map。)不要使用forEach,如果回调不是异步工作,希望forEach等到工作完成(因为不会)。
但是还有很多东西要探索,请继续阅读…# `+ |4 F; d6 w+ ?& e/ S' D
JavaScript 有很强的语义来循环遍历数组和类数组对象。我将答案分为两部分:真实数组的选项和类似例如,数组选项arguments其他可迭代对象 (ES2015 )、DOM 集合等。) T# B; A5 }+ z# d
好吧,让我们看看我们的选择:
& J( s& p& K3 K; [) o对际数组你有五个选项(两个基本上是永久性的,另一个是 ECMAScript 5 [“ES5 添加,另外两个在 ECMAScript 2015(“ES2015”,又名“ES6”)中添加:7 g; ?. ?4 O6 K* L2 ^$ }2 ~
[ol]使用for-of(隐式使用迭代器)(ES2015 )
) ]8 p0 p5 L( a7 N3 G3 g使用forEach及相关(ES5 )
# x  W6 q% y2 a2 y  U; I使用简单的for循环
! Y; Q7 i( W4 Q2 E. b' U正确使用for-in! t7 f7 U# i( Q
迭代器用于显式(ES2015 )[/ol](你可以看到这里的旧规范:ES5,ES2015年,但两者都被取代了;目前编辑的草稿总是在这里。
! p* _: @! y5 a. {6 V细节:
* E  E! ~- }& Y! @) f5 }& O1.使用for-of(隐式使用迭代器)(ES2015 )ES2015向 JavaScript添加了迭代器和可迭代对象。数组是可迭代的(字符串,Maps 和Sets 以及 DOM 集合和列表也是如此,以后你会看到的)。迭代对象为其值提供迭代器。newfor-of语句循环遍历迭代器返回的值:
4 J* S9 B( J6 D; c& g
    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* E( }9 [/ ]% t+ I" g8 g% A( o
没有比这更简单的了!在幕后,它从数组中获得一个迭代器,并通过迭代器返回。数组提供的迭代器从头到尾提供数组元素的值。& K/ |+ h2 s: F* q8 ?* Q
注意element每个循环迭代的范围是什么;尝试element循环结束后使用失败,因为它不存在于循环体之外。
) B' o- E0 T+ L理论上,一个for-of循环涉及多个函数调用(一个用于获取迭代器,另一个用于获取每个值)。即使这是真的,也没有什么可担心的。JavaScript 引擎中的函数调用非常便宜(它困扰着我forEach[below] 直到我研究它;细节)。但此外,在处理数组等本机迭代器时,JavaScript 发动机将优化这些调用程序关键性能代码中)。
) U2 `7 S- o% E8 O- wfor-of是完全async友好的。如果你需要在循环中串联(而不是并行)来完成工作,那么循环中的一个awaitin 循环体将等待承诺在继续之前解决。这是一个愚蠢的例子:8 b$ n6 O: Z% H* e# P9 R/ d
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请注意这些词是如何在每个词之前延迟的。
; U4 @4 x5 K& d% y  i这是编码风格的问题,但是for-of这是我在遍历任何可迭代的东西时首先要接触的东西。
( B1 W  b1 u4 w1 `* P4 J; X; S2.用途forEach及相关在任何可访问的地方ArrayES5 添加的功能模糊了现代环境(因此,不是 IE8)如果只处理同步代码(或者不需要等待),可以使用forEach( spec | MDN )循环期间完成的异步过程:
& z; e  H/ G+ `, G) ?$ d
    const a = ["a","b","c"];a.forEach((element) =>    console.log(element);});
    3 ~* a* V7 z  e4 H/ j' f
forEach接受回调函数和可选值this调用回调时使用的值(上述未使用)。调用数组中的每个元素,并按顺序跳过稀疏数组中不存在的元素。虽然我只使用了上述参数,但回调函数调用了三个参数:迭代元素、元素索引和正在迭代的数组(以防止您的函数不方便)。
" j" K  n0 A& H) t7 ?像for-of,forEach优点是不需要在包含范围内声明索引和值变量;在这种情况下,它们被提供为迭代函数的参数,并且很好地限制在迭代中。& A, n) Q1 }& D  Q
与for-of,forEach缺点是不懂async函数和await.如果您使用 async函数作为回调,forEach则在继续之前等待函数的承诺解决。这是替代的async示例- 请注意初始延迟是如何发生的,但所有文本都会立即出现,而不是等待:for-of``forEach" }8 Y" T# D  ?: e. I% I9 V
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 rejectsforEach 是循环遍历所有函数,但 ES5 定义了其他几个有用的遍历数组并执行操作函数,包括:
/ J5 h' z& v- l% l0 k0 s" Y  {every( spec | MDN ) - 第一次回调假值时停止循环
4 V& G+ }. x2 w+ jsome( spec | MDN ) - 回调第一次回到真实值时停止循环
/ v- c: e, D, r& T- [, p: v# U6 _filter( spec | MDN ) - 创建一个新的数组,包括回调返回真实值的元素,省略不返回真实值的元素/ d- U% e+ p3 U
map( spec | MDN ) - 从回调返回的值创建一个新数组
$ h- o0 X  w$ ireduce( spec | MDN ) - 通过重复调用回调建立一个值,并将其传输到以前的值;详情请参考规范
- `, v, S' z4 @0 G/ NreduceRight( spec | MDN ) - 类似reduce,但是按照降序而不是升序
和 一样forEach,若使用一个async作为您的回调函数,函数不会等待函数的承诺完成。这意味着:( E+ m+ v: O* o$ b
使用一个async回调函数从未通过适当的回调函数every,some以及filter因为他们将承诺返回,就像一个truthy值; 他们不会等待承诺解决,然后使用履行价值。4 T( |8 m+ D) M: W7 ?! V" q6 K
使用async函数回调通常适用于map,如果目标是将某个数组转换为promise可能是数组传递给 promise 组合器函数(Promise.all、Promise.race、promise.allSettled、 或Promise.any)。
5 T# D) i9 y7 ^使用async函数回调很少适合reduceor reduceRight,因为(再次)回调将永远回到承诺中。但有一种习惯用法是从使用中使用。reduce( const promise = array.reduce((p,element) => p.then(/*...something usingelement...*/);)数组构建承诺链,但通常在这些情况下,函数中for-oforfor循环async调试更清晰、更容易。
3. 使用简单for循环有时旧的方法是最好的:
( h% X5 _  [1 o# O2 T
    $ w$ D1 h- N' Y% d
  • const a = ["a","b","c"];for (let index = 0; index 如果数组的长度在循环过程中不会改变,那么它可能是一个稍微复杂的版本,在高性能敏感的代码中抓住长度达阵很小的有点快:) G4 V7 k1 i- m, h. i
  • [code]const a = ["a","b","c"];for (let index = 0,len = a.length; index And/or 倒数:[code]const a = ["a","b","c"];for (let index = a.length - 1; index >= 0; --index)    const element = a[index];    console.log(element);}. y7 {+ l3 f: \  w
但现代 JavaScript 引擎,您很少需要勉强挤出最后一点力气。
" @2 D6 f) ~) ]. [: {5 c1 v在 ES在2015 之前,包含作用域必须存在循环变量var只有函数级作用域,没有块级作用域。但正如你在上面的例子中看到的,你可以let在 内使用for将变量范围限制为循环。index每次循环迭代都会重新创建变量,这意味着在循环中创建的闭包将被保留index引用特定迭代,解决了旧的循环封闭问题:
$ ^" V4 P  l  f0 C4 o, E$ T" \* q// (The `NodeList` from `querySelectorAll` is array-like)const divs = document.querySelectorAll("div");for (let index = 0; index         console.log("Index is: "   index);}zeroonetwothreefour如果你点击上面的第一个,你会得到Index is: 0Index is: 4”。这并没有,假如你使用的工作var,而不是let(你总是看:5指数)。! L6 P0 J; R3 O& F
就像for-of,for循环在async函数运行良好。这是使用。for早期循环示例:
3 X9 e: O" ]* nShow code snippet
: [  [& y* x) o7 g- K4、正确使用for-infor-in它不是用于遍历数组,而是用于遍历对象属性的名称。作为对象的副产品,它似乎经常用于循环遍历数组,但它不仅循环遍历数组索引,还循环遍历对象所有可枚举属性(包括继承属性)。4 L3 l5 B8 W3 v
for-in数组中唯一真实的用例是:7 q' E+ B) J) o1 E  B# z# @, J6 r
它是一个稀疏其中有数组大量间隙,或者
* w$ U( K8 w& J! a在数组对象中使用非元素属性,并希望将其包含在循环中
查看第一个示例:for-in若使用适当的保护措施,可使用访问稀疏数组元素:/ }; D8 U2 L7 }) O  n  D
    ( C6 C" G7 V5 `; X" `  F
  • // `a` is a sparse arrayconst 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 注意三项检查:
    8 A$ }* ^* ], P+ u
  • [ol]1 v1 x) R$ p4 A
  • 对象有这个名字自己的属性(不是它的原型继承的属性;a.hasOwnProperty(name)但 ES2022 添加了Object.hasOwn更可靠),
    / y1 s2 X. Q- M
  • 名称为十进制数字(如正常字符串形式,而不是科学记数法),以及( C7 g* E! M' [' F) L3 ]
  • 数字时名称的强制值为 [/ol]…尽管如此,大多数代码只进行hasOwnProperty检查。
    8 x, k4 Y( i9 a  a
  • 当然,你不会在内联代码中这样做。您将编写实用程序函数。
    8 W9 Z4 {" A3 |) m% V
  • // 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     console.log("Value at "   index   " is "   value);});像for,for-in若工作需串行完成,则在异步函数中效果良好。$ u3 q% C) p8 A5 C* r' o
  • 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迭代器用于显式(ES2015 )for-of隐式使用迭代器为您完成所有 scut 工作。有时候,你可能希望显式使用迭代器。它看起来像这样:[code]const a = ["a","b","c"];const it = a.values(); // Or `const it = a[Symbol.iterator]();` if you likelet entry;while (!(entry = it.next()).done) {    const element = entry.value;    console.log(element);}
    . i; N7 m6 B2 ?' d+ I, K! Q- o+ g
迭代器是符合规范中迭代器定义的对象。每次调用它next在方法上,它会回到一个新的结果对象。结果对象有属性 ,done告诉我们它是否完成了,还有另一个属性value包含迭代值。(done如果是false,value是可选的,如果是,是可选的undefined。); j2 R1 o& Z- o1 P- o1 R* B' P
你得到的value这取决于迭代器。在阵列中,缺少迭代器提供每个阵列元素的值("a","b",和"c"前面的例子)。数组还有三种返回迭代器的方法:
, G0 d4 P+ b6 J( T9 b! kvalues():这是[Symbol.iterator]返回默认迭代器方法的别名。
, X8 V0 m- k) k' bkeys(): 返回一个迭代器,它在数组中提供每个键(索引)。它将在上述示例中提供"0",then "1",then "2"(是的,作为字符串)。
& k. S" ~8 d7 K# f2 R& l& mentries(): 返回一个提供[key,value]数组的迭代器。
因为迭代器对象在调用 之前不会前进next,因此它们在async函数循环运行良好。这是以前的for-of明确使用迭代器的示例:$ s0 A" q" e4 V4 q
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属性及全数字名称属性:NodeListinstances、HTMLCollectioninstances、argumentsobject 等等。我们如何遍历他们的内容?' v; q' [- f& G" A3 w& i. f, x
使用上述大多数选项至少有一些上述数组方法,可能是大多数甚至全部,也适用于类似数组的对象:
9 J3 X  B- s# r7 p; f[ol]使用for-of(隐式使用迭代器)(ES2015 )[/ol]for-of使用对象提供的迭代器(如有)。这包括主机提供的对象(如 DOM 集合和列表)。HTMLCollection来自getElementsByXYZ方法NodeList例子和来源querySelectorAll两者的s 实例支持迭代。(这是 HTML 和 DOM 规范非常巧妙定义。基本上,任何东西都有length和 索引访问的对象是自动可迭代的。不必被标记iterable;除了用于集合,除了可迭代,还支持forEach,values,keys,和entriesmethods. NodeListdo; HTMLCollectionnot,但两者都是可迭代的。: }- y! z& n, `/ |7 m
以下是循环遍历div元素的例子:7 h: P- |3 }( S
Show code snippet
" ^  ~/ M7 g8 c7 \& ]5 b! ~[ol]使用forEach及相关(ES5 )[/ol]on 各种函数Array.prototype可通过有意通用Function#call( spec | MDN ) 或Function#apply( spec | MDN )用于类数组对象。(如果你必须处理 IE8 或更早版本 [哎哟],请参阅本答案末尾的主机提供对象的警告,但这对模糊的现代浏览器来说不是问题。
" z( }  s6 l5 i6 x) g( x假设您想forEach在 aNode的childNodes集合使用(作为 ,本机HTMLCollection没有forEach)。你会这样做:3 ^5 Q, p& L# L0 }
js   Array.prototype.forEach.call(node.childNodes,(child) => {       // Do something with `child`  };
5 V0 }- D% o* N) t9 w, t' e& O# }(但请注意,您只能使用它for-ofon node.childNodes。)
( ^3 U( b5 X- N, @# |* W如果您计划经常这样做,您可能希望将函数引用的副本抓取到变量中进行重用,例如:# }8 |+ m' h8 J$ ^
```js8 W1 e) x. ~) R" ^
            // (This is all presumably in a module or some scoping function)2 d# I7 W( }6 c. Y  w
            const forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);" p7 S- f, y6 [. I* x
// Then later…; w8 {" O! @) y" ~3 T
            forEach(node.childNodes,(child) => {
- w' K" P: h9 b5 v                            Do something with child9 h3 J9 ~4 v  x$ C; W* Z) N8 A
            };
; j, w* }! b6 _& b2 c            ```# E  R' j4 J! l2 a
[ol]使用简单的for循环[/ol]也许很明显,简单for循环适用于类似数组的对象。% a- [: p# Q) k- I* ?5 v# I3 P+ i. A
[ol]迭代器用于显式(ES2015 )[/ol]见#1。
7 G: j" }& k; x# G可能能够逃脱for-in(使用安全措施),但没有理由尝试所有这些更合适的选项。
8 ?  W" Z; _' v( p6 Q  Z3 w) r创建一个真正的数组在其他情况下,您可能希望将类似数组的对象转换为真实数组。这样做很简单:) Z0 \3 J  |. F5 z9 i
[ol]利用 Array.from[/ol]Array.from (规格) | (MDN)(ES2015 ,但容易填充)从类似数组的对象中创建数组,可以选择先通过映射函数传输条目。. j' J. i/ _5 {9 t0 t/ I
js   const divs = Array.from(document.querySelectorAll("div"));8 B: H7 j" J( F  }) t' X' s
…获取NodeListfromquerySelectorAll并创建数组。
$ r  _% P4 r2 v" V6 u如果你打算以某种方式映射内容,映射功能将非常方便。例如,如果您想获得具有给定类别元素的标签名称数组:
2 T3 J  _2 d" g0 z+ s, W* q```js9 b" ^' g/ [& j& ~5 S& e" s
            // Typical use (with an arrow function):) f; S  m, ~6 B6 Q: [+ ~8 z0 j! s
            const divs = Array.from(document.querySelectorAll(“.some-class”),element => element.tagName);6 y  \4 I% ?) h0 h4 Y
// Traditional function (since Array.from can be polyfilled):& Y2 b7 s, I7 w) y7 @
            var divs = Array.from(document.querySelectorAll(“.some-class”),function(element) {
. u, X6 q, Q& M$ b; r& Q4 s                            return element.tagName;: J( d2 {/ i7 @7 q& w% F
            };
- H, ^) m' z: l+ u3 ]7 p1 g            ```
+ `* X# [. P# ^( l4 ?9 Z9 I% g9 K, J[ol]使用扩展语法 ( ...)[/ol]也可使用 ES2015 扩展语法。for-of,使用对象提供的迭代器(见上节 #1):
' e- f0 C7 b2 \1 b& Zjs   const trueArray = [...iterableObject];
' E" s$ Q2 V' w" J9 L例如,如果我们想要 a 转换NodeList对于一个真正的数组来说,使用扩展语法将变得非常简单:" d4 S) [% `: U- h7 B+ U
js   const divs = [...document.querySelectorAll("div")];
+ A* u& N; F8 O( b  l4 m% {[ol]使用slice数组的方法[/ol]我们可以用slice和上面提到的其他方法一样,数组的方法是有意通用的,所以可以和类似数组的对象一起使用,如下所示:+ `) `* I* y+ _6 _" I
js   const trueArray = Array.prototype.slice.call(arrayLikeObject);9 U! [0 t! R  J0 L3 l2 G) q1 v
例如,如果我们想要 aNodeList我们可以把它转换成真正的数组:  O& ~4 r4 x* S  r" v: K
js   const divs = Array.prototype.slice.call(document.querySelectorAll("div"));( g; ]! ~0 U7 `2 i: o1 n) N
(如果你还必须处理 IE8 [哎哟],会失败;IE8 不允许你像this使用主机提供的对象。3 J2 L) l2 ?& Q- O1 g; w
主机提供的对象的警告如果您使用主机提供类似数组的对象Array.prototype函数(例如,DOM 由浏览器组成,而不是 JavaScript 发动机提供),如 IE8 过时的浏览器可能无法处理,所以如果你必须支持它们,请确保在你的目标环境中进行测试。但这对模糊的现代浏览器来说并不是一个问题。(对于非浏览器环境,它自然会因环境而异。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则