JavaScript forEach 按数组顺序执行的三个维度分析
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">1. 同步代码:严格顺序执行</a></li><li><a href="#_label1">2. 异步代码:顺序启动,但不顺序等待(最常见的坑)</a></li><li><a href="#_label2">3. 特殊规则:跳过“稀疏”位置</a></li><li><a href="#_label3">总结与最佳实践</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_0">方法 A:使用for...of(推荐)</a></li><li><a href="#_lab2_3_1">方法 B:使用reduce</a></li><li><a href="#_lab2_3_2">方法 C:如果你不需要顺序,只需要并发</a></li></ul></ul></div><p>简单直接的回答是:是的,在同步代码中,<code>forEach</code> 是严格按照数组索引顺序(从 0 到 length-1)执行的。</p><p>但这里有一个巨大的陷阱:虽然它按顺序“启动”任务,但它不会等待异步操作(如 <code>Promise</code>)完成。</p>
<p>下面从三个维度详细分析:</p>
<p class="maodian"><a name="_label0"></a></p><h2>1. 同步代码:严格顺序执行</h2>
<p>如果你在 <code>forEach</code> 中执行的是纯同步逻辑,它会从头到尾依次执行:</p>
<div class="jb51code"><pre class="brush:js;">const arr = ;
arr.forEach(num => {
console.log(num);
});
// 输出顺序永远是: 1, 2, 3</pre></div>
<p class="maodian"><a name="_label1"></a></p><h2>2. 异步代码:顺序启动,但不顺序等待(最常见的坑)</h2>
<p>当你把 <code>async/await</code> 放在 <code>forEach</code> 中时,<code>forEach</code> <strong>不会</strong>等待上一个循环的任务完成才开始下一个。</p>
<div class="jb51code"><pre class="brush:js;">const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const arr = ;
async function test() {
console.log('开始');
arr.forEach(async (num) => {
await delay(1000 / num); // 不同的延迟时间
console.log(num);
});
console.log('结束');
}
test();
// 实际输出顺序:
// 开始
// 结束
// 3 (延时最短,先完成)
// 2
// 1 (延时最长,最后完成)</pre></div>
<p><strong>原因:</strong> <code>forEach</code> 内部的逻辑大致是这样的:<code>for (let i = 0; i < len; i++) { callback(arr); }</code>。它只是简单地调用了回调函数,并没有 <code>await</code> 回调函数的返回值。</p>
<p class="maodian"><a name="_label2"></a></p><h2>3. 特殊规则:跳过“稀疏”位置</h2>
<p><code>forEach</code> 会跳过数组中已删除或未赋值的项(空位),但不会跳过 <code>undefined</code>。</p>
<div class="jb51code"><pre class="brush:js;">const arr = ; // 索引 1 是空位
arr.forEach((num, i) => console.log(i, num));
// 输出:
// 0 1
// 2 3
// (跳过了索引 1)</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>总结与最佳实践</h2>
<p>如果你需要<strong>严格按顺序执行异步任务</strong>(即:任务1完成 -> 任务2开始),请<strong>不要</strong>使用 <code>forEach</code>,改用以下方式:</p>
<p class="maodian"><a name="_lab2_3_0"></a></p><h3>方法 A:使用for...of(推荐)</h3>
<p><code>for...of</code> 能够正确处理 <code>await</code>。</p>
<div class="jb51code"><pre class="brush:js;">for (const num of arr) {
await doSomethingAsync(num); // 这里会严格等待
}
</pre></div>
<p class="maodian"><a name="_lab2_3_1"></a></p><h3>方法 B:使用reduce</h3>
<div class="jb51code"><pre class="brush:js;">arr.reduce(async (promise, num) => {
await promise;
await doSomethingAsync(num);
}, Promise.resolve());
</pre></div>
<p class="maodian"><a name="_lab2_3_2"></a></p><h3>方法 C:如果你不需要顺序,只需要并发</h3>
<p>如果你希望所有任务<strong>同时开始</strong>并等待全部结束,使用 <code>map</code> 配合 <code>Promise.all</code>:</p>
<div class="jb51code"><pre class="brush:js;">await Promise.all(arr.map(async (num) => {
await doSomethingAsync(num);
}));
</pre></div>
<p><strong>结论:</strong> 在处理同步逻辑时,<code>forEach</code> 是顺序的;在处理 <code>async/await</code> 异步逻辑时,它会导致“并发”执行(实际上是顺序启动,乱序结束),这通常不是你想要的结果。</p>
頁:
[1]