Loop (for each) over an array in JavaScript Ask Question

Loop (for each) over an array in JavaScript Ask Question

How can I loop through all the entries in an array using JavaScript?

ベストアンサー1

TL;DR

  • Your best bets are usually

    • a 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`...
      });
      
    • a simple old-fashioned for loop - async-friendly
      for (let index = 0; index < theArray.length; ++index) {
          const element = theArray[index];
          // ...use `element`...
      }
      
    • (rarely) 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:

    • Don't use for-in unless you use it with safeguards or are at least aware of why it might bite you.
    • Don't use map if you're not using its return value.
      (There's sadly someone out there teaching 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.)
    • Don't use forEach if the callback does asynchronous work and you want the forEach to wait until that work is done (because it won't).

But there's lots more to explore, read on...


JavaScript has powerful semantics for looping through arrays and array-like objects. I've split the answer into two parts: Options for genuine arrays, and options for things that are just array-like, such as the arguments object, other iterable objects (ES2015+), DOM collections, and so on.

Okay, let's look at our options:

For Actual Arrays

You have five options (two supported basically forever, another added by ECMAScript 5 ["ES5"], and two more added in ECMAScript 2015 ("ES2015", aka "ES6"):

  1. Use for-of (use an iterator implicitly) (ES2015+)
  2. Use forEach and related (ES5+)
  3. Use a simple for loop
  4. Use for-in correctly
  5. Use an iterator explicitly (ES2015+)

(You can see those old specs here: ES5, ES2015, but both have been superceded; the current editor's draft is always here.)

Details:

1. Use for-of (use an iterator implicitly) (ES2015+)

ES2015 added iterators and iterables to JavaScript. Arrays are iterable (so are strings, Maps, and Sets, as well as DOM collections and lists, as you'll see later). Iterable objects provide iterators for their values. The new for-of statement loops through the values returned by an iterator:

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

It doesn't get simpler than that! Under the covers, that gets an iterator from the array and loops through the values the iterator returns. The iterator provided by arrays provides the values of the array elements, in order beginning to end.

Notice how element is scoped to each loop iteration; trying to use element after the end of the loop would fail because it doesn't exist outside the loop body.

In theory, a for-of loop involves several function calls (one to get the iterator, then one to get each value from it). Even when that's true, it's nothing to worry about, function calls are very cheap in modern JavaScript engines (it bothered me for forEach [below] until I looked into it; details). But additionally, JavaScript engines optimize those calls away (in performance-critical code) when dealing with native iterators for things like arrays.

for-of is entirely async-friendly. If you need the work in a loop body to be done in series (not in parallel), an await in the loop body will wait for the promise to settle before continuing. Here's a silly example:

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

Note how the words appear with a delay before each one.

It's a matter of coding style, but for-of is the first thing I reach for when looping through anything iterable.

2. Use forEach and related

In any even vaguely-modern environment (so, not IE8) where you have access to the Array features added by ES5, you can use forEach (spec | MDN) if you're only dealing with synchronous code (or you don't need to wait for an asynchronous process to finish during the loop):

const a = ["a", "b", "c"];
a.forEach((element) => {
    console.log(element);
});

forEach accepts a callback function and, optionally, a value to use as this when calling that callback (not used above). The callback is called for each element in the array, in order, skipping non-existent elements in sparse arrays. Although I only used one parameter above, the callback is called with three arguments: The element for that iteration, the index of that element, and a reference to the array you're iterating over (in case your function doesn't already have it handy).

Like for-of, forEach has the advantage that you don't have to declare indexing and value variables in the containing scope; in this case, they're supplied as arguments to the iteration function, and so nicely scoped to just that iteration.

Unlike for-of, forEach has the disadvantage that it doesn't understand async functions and await. If you use an async function as the callback, forEach does not wait for that function's promise to settle before continuing. Here's the async example from for-of using forEach instead — notice how there's an initial delay, but then all the text appears right away instead of waiting:

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 is the "loop through them all" function, but ES5 defined several other useful "work your way through the array and do things" functions, including:

  • every (spec | MDN) - stops looping the first time the callback returns a falsy value
  • some (spec | MDN) - stops looping the first time the callback returns a truthy value
  • filter (spec | MDN) - creates a new array including elements where the callback returns a truthy value, omitting the ones where it doesn't
  • map (spec | MDN) - creates a new array from the values returned by the callback
  • reduce (spec | MDN) - builds up a value by repeatedly calling the callback, passing in previous values; see the spec for the details
  • reduceRight (spec | MDN) - like reduce, but works in descending rather than ascending order

As with forEach, if you use an async function as your callback, none of those waits for the function's promise to settle. That means:

  • Using an async function callback is never appropriate with every, some, and filter since they will treat the returned promise as though it were a truthy value; they don't wait for the promise to settle and then use the fulfillment value.
  • Using an async function callback is often appropriate with map, if the goal is to turn an array of something into an array of promises, perhaps for passing to one of the promise combinator functions (Promise.all, Promise.race, promise.allSettled, or Promise.any).
  • Using an async function callback is rarely appropriate with reduce or reduceRight, because (again) the callback will always return a promise. But there is an idiom of building a chain of promises from an array that uses reduce (const promise = array.reduce((p, element) => p.then(/*...something using `element`...*/));), but usually in those cases a for-of or for loop in an async function will be clearer and easier to debug.

3. Use a simple for loop

Sometimes the old ways are the best:

const a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    const element = a[index];
    console.log(element);
}

If the length of the array won't change during the loop, and it's in highly performance-sensitive code, a slightly more complicated version grabbing the length up front might be a tiny bit faster:

const a = ["a", "b", "c"];
for (let index = 0, len = a.length; index < len; ++index) {
    const element = a[index];
    console.log(element);
}

And/or counting backward:

const a = ["a", "b", "c"];
for (let index = a.length - 1; index >= 0; --index) {
    const element = a[index];
    console.log(element);
}

But with modern JavaScript engines, it's rare you need to eke out that last bit of juice.

Before ES2015, the loop variable had to exist in the containing scope, because var only has function-level scope, not block-level scope. But as you saw in the examples above, you can use let within the for to scope the variables to just the loop. And when you do that, the index variable is recreated for each loop iteration, meaning closures created in the loop body keep a reference to the index for that specific iteration, which solves the old "closures in loops" problem:

// (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>

In the above, you get "Index is: 0" if you click the first and "Index is: 4" if you click the last. This does not work if you use var instead of let (you'd always see "Index is: 5").

Like for-of, for loops work well in async functions. Here's the earlier example using a for loop:

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

4.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]);
    }
}

次の 3 つのチェックに注意してください。

  1. オブジェクトがその名前の独自のプロパティを持っていること(プロトタイプから継承したものではないこと。このチェックは次のようにも書かれることが多いが、a.hasOwnProperty(name)ES2022では次のように追加されている。Object.hasOwnより信頼性が高い)

  2. 名前はすべて10進数であること(例:科学的記数法ではなく通常の文字列形式)、および

  3. 名前の値を数値に強制変換すると、<= 2^32 - 2 (4,294,967,294) になります。この数字はどこから来るのでしょうか? これは配列インデックスの定義の一部です。仕様書ではその他の数値 (非整数、負の数、2^32 - 2 より大きい数) は配列のインデックスではありません。2^32 - 2である理由は、最大のインデックス値が配列が持つことができる最大値である2^32 - 1より 1 小さくなるためです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

5. イテレータを明示的に使用する (ES2015+)

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プロパティがあります。(の場合は はオプションの場合は はオプションです。)valuedonefalsevalueundefined

で得られる結果は、イテレータによって異なります。配列の場合、デフォルトのイテレータは各配列要素 (前の例では、、、)valueの値を提供します。配列には、イテレータを返す他の 3 つのメソッドもあります。"a""b""c"

  • values()[Symbol.iterator]: これは、デフォルトのイテレータを返すメソッドのエイリアスです。
  • keys(): 配列内の各キー(インデックス)を提供するイテレータを返します。上記の例では、 、 、 (文字列ではなく数値として)を提供します012また、スパース配列では、意思存在しない要素のインデックスを含めます。
  • 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

配列のようなオブジェクトの場合

真の配列の他に、すべて数字の名前を持つプロパティとプロパティを持つ配列のようなオブジェクトもあります。lengthNodeListインスタンスHTMLCollectionインスタンスargumentsオブジェクトなど。その内容をループするにはどうすればよいでしょうか。

上記のオプションのほとんどを使用する

上記の配列アプローチの少なくとも一部、おそらくほとんど、あるいはすべてが、配列のようなオブジェクトにも同様に適用されます。

  1. 使用for-of(暗黙的にイテレータを使用する)(ES2015+)

    for-of使用反復子オブジェクトによって提供されるもの(ある場合)です。これには、ホストが提供するオブジェクト(DOM コレクションやリストなど)が含まれます。たとえば、メソッドHTMLCollectionからのインスタンスと の両方からのインスタンスは反復をサポートします。(これは、HTML および DOM 仕様によって非常に微妙に定義されています。基本的に、およびインデックス アクセスを持つオブジェクトは自動的に反復可能です。をマークする必要はありません。これは、反復可能であることに加えて、、、、およびメソッドをサポートするコレクションにのみ使用されます。は をサポートは をサポートしませんが、どちらも反復可能です。)getElementsByXYZNodeListquerySelectorAlllengthiterableforEachvalueskeysentriesNodeListHTMLCollection

    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>

  1. 使用forEachと関連(ES5+)

    のさまざまな関数はArray.prototype「意図的に汎用的」であり、配列のようなオブジェクトに対してFunction#callスペック|翻訳) またはFunction#applyスペック|翻訳)。(IE8 以前を扱う必要がある場合 [痛い]、この回答の最後にある「ホスト提供のオブジェクトに関する注意事項」を参照してください。ただし、これは比較的新しいブラウザでは問題になりません。)

    コレクション ( であるため、ネイティブにはがありません)forEachを使用したいとします。次のようにします。NodechildNodesHTMLCollectionforEach

    Array.prototype.forEach.call(node.childNodes, (child) => {
        // Do something with `child`
    });
    

    (ただし、for-ofon を使用することもできますnode.childNodes。)

    これを頻繁に行う場合は、関数参照のコピーを変数に保存して再利用することをお勧めします。例:

    // (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`
    });
    
  2. シンプルなforループを使用する

    おそらく明らかなことですが、for配列のようなオブジェクトでは単純なループが機能します。

  3. イテレータを明示的に使用する (ES2015+)

    #1を参照してください。

安全策を講じれば、何とかなるかもしれませfor-inが、より適切な選択肢がたくさんあるので、試す理由はありません。

真の配列を作成する

場合によっては、配列のようなオブジェクトを真の配列に変換したいこともあります。これは驚くほど簡単です。

  1. 使用Array.from

    Array.from (スペック)|(MDN)(ES2015+ ですが、簡単にポリフィルできます) 配列のようなオブジェクトから配列を作成し、オプションでエントリを最初にマッピング関数に渡します。つまり、

    const divs = Array.from(document.querySelectorAll("div"));
    

    NodeList...から取得しquerySelectorAll、そこから配列を作成します。

    マッピング関数は、何らかの方法でコンテンツをマッピングする場合に便利です。たとえば、特定のクラスの要素のタグ名の配列を取得したい場合:

    // 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;
    });
    
  2. スプレッド構文を使用する(...

    ES2015のスプレッド構文for-ofこれは、反復子オブジェクトによって提供されるもの(前のセクションの #1 を参照):

    const trueArray = [...iterableObject];
    

    たとえば、a をNodeList真の配列に変換したい場合、スプレッド構文を使用すると非常に簡潔になります。

    const divs = [...document.querySelectorAll("div")];
    
  3. slice配列メソッドを使用する

    私たちはslice配列のメソッドは、上記の他のメソッドと同様に「意図的に汎用的」であるため、次のように配列のようなオブジェクトで使用できます。

    const trueArray = Array.prototype.slice.call(arrayLikeObject);
    

    たとえば、 a をNodeList真の配列に変換したい場合は、次のようにします。

    const divs = Array.prototype.slice.call(document.querySelectorAll("div"));
    

    (まだ IE8 を扱わなければならない場合 [痛い]、失敗します。IE8 では、ホストが提供するオブジェクトをそのように使用できませんでしたthis。)

ホストが提供するオブジェクトに関する注意事項

ホストが提供するArray.prototype配列のようなオブジェクト (たとえば、JavaScript エンジンではなくブラウザーが提供する DOM コレクションなど)で関数を使用する場合、IE8 などの古いブラウザーでは必ずしもそのように処理されないため、それらをサポートする必要がある場合は、ターゲット環境で必ずテストしてください。ただし、最新のブラウザーでは問題になりません。(ブラウザー以外の環境の場合は、当然、環境によって異なります。)

おすすめ記事