【javaScript】for…in、for…of、forEachの違いと用途

フロントエンド

ループの方法が多すぎるし、それぞれちょっとずつ違いがあって全然覚えられないためまとめておきます。

for…in

列挙可能であればなんでもループできる
順不同であることに注意
・オブジェクトのループ専用として利用した方が良い
・普通の配列のループには不向き

⬇オブジェクトをfor…inループする例

const obj = {key1: 'value1', key2: 'value2', key3: 'value3'};

for(let key in obj) {
    console.log(key + ' ' + obj[key]);
}
key1 value1
key2 value2
key3 value3

for…inで変数に入るのはオブジェクトのキーのみです。値はobj[key]のように取得できますが、少々使いにくいかもしれません。
順不同という仕様ですが、この例では配列の順序通り出力されました。たまたまなのか、順不同になる条件があるのか…?

⬇普通の配列をfor…inループする例

const array = ['value1', 'value2', 'value3'];

for(let index in array) {
    console.log(index + ' ' + array[index]);
}
0 value1
1 value2
2 value3

こちらも変数にはインデックスしか入りません。順序を保証しないため、配列のループには不向きです。

for…inループの使いにくいところ

列挙可能なプロパティはすべてループの対象にしていまいます。以下の例では、ObjectクラスとArrayクラスを少し拡張後に、連想配列と普通の配列をfor…inでループしています。

Object.prototype.hoge = 'hogeValue';
Array.prototype.fuga = 'fugaValue';

const obj = {key1: 'value1', key2: 'value2', key3: 'value3'};

for(let o in obj) {
    console.log(o);
}

const array = ['value1', 'value2', 'value3'];

for(let a in array) {
    console.log(a);
}
key1 
key2 
key3 
hoge 
0 
1 
2 
fuga 
hoge

結果を見るとObjectに追加したhoge関数と、Arrayに追加したfuga関数までループの対象になっています。

⬇これを回避する方法があります。

Object.prototype.hoge = 'hogeValue';
Array.prototype.fuga = 'fugaValue';

const obj = {key1: 'value1', key2: 'value2', key3: 'value3'};

for(let o in obj) {
    if (obj.hasOwnProperty(o)) {
        console.log(o);
    }
}

const array = ['value1', 'value2', 'value3'];

for(let a in array) {
    if (array.hasOwnProperty(a)) {
        console.log(a);
    }
}
key1
key2
key3
0
1
2

hasOwnPropertyObject.prototypeに定義されているメソッドで、自分自身が指定されたプロパティを持っている場合にtrueを返します。

for…of

反復可能な要素をループできる
ES6の仕様
・IEではサポートされていないので注意
・String、Array、Map、NodeListなど様々なものをループできる
・オブジェクトのループはできない

⬇普通の配列をfor…ofループする例

const array = ['value1', 'value2', 'value3'];

for(let value of array) {
    console.log(value);
}
value1
value2
value3

for…inと違って変数にはインデックスではなく値が入るため、使いやすいです。

⬇for…ofでオブジェクトのループはできません。

const obj = {key1: 'value1', key2: 'value2', key3: 'value3'};

for(let o of obj) {    // TypeError: obj is not iterable
    console.log(o);
}

for…ofでインデックスがほしいとき

配列の場合は以下のようにArrayentriesを利用するとインデックスと値のペアをループすることができます。が、後ほど紹介するforEachを使ったほうが綺麗です。

const array = ['value1', 'value2', 'value3', 'value4', 'value5'];

for(let [index, value] of array.entries()) {
    console.log(index + value);
}

NodeListをfor…ofでループ

querySelectorAllの返り値であるNodeListもfor…ofでループできます。

<ul id="list">
  <li>aaa</li>
  <li>bbb</li>
  <li>ccc</li>
</ul>
let list = document.querySelectorAll('#list li');
for (let el of list) {
  console.log(el);
}

⬇インテックスがほしいときは以下のようにentriesを使ってインデックスと値のペアをループすることができます。が、後ほど紹介するforEachを使ったほうが綺麗です。

let list = document.querySelectorAll('#list li');
for (let [index, value] of list.entries()) {
  console.log(index);
  console.log(value);
}

NodeListentriesは2020年9月の現時点でIEには対応していませんのでご注意ください。

HTMLCollectionをfor…ofでループ

getElementsByClassName.childrenの返り値であるHTMLCollectionもfor…ofでループできます。

<ul id="list">
  <li>aaa</li>
  <li>bbb</li>
  <li>ccc</li>
</ul>
let ul = document.getElementById('list');
for (let li of ul.children) {
  console.log(li);
}

この方法ではインデックスを得る方法はなさそうです。単純なforループを利用するか、HTMLCollectionを強引に配列に変換する方法もあるようです。

forEach

・Array.prototypeに定義されているメソッド
・for、for…in、for…ofのようなループ構文ではなく、ただのメソッドであることに注意
・配列と配列ライクなものをループできる
・ループの処理を途中で止めることはできない

const array = ['value1', 'value2', 'value3'];

array.forEach(function(elem) {
    console.log(elem);
});
value1
value2
value3

⬇forEachループでindexがほしいとき

const array = ['value1', 'value2', 'value3'];

array.forEach(function(elem, index) {
    console.log(index + ' ' + elem);
});
0 value1
1 value2
2 value3

NodeListをforEachでループ

<ul id="list">
  <li>aaa</li>
  <li>bbb</li>
  <li>ccc</li>
</ul>
let list = document.querySelectorAll('#list li');
list.forEach((elem, index) => {
  console.log(index);
  console.log(elem);
});

HTMLCollectionをforEachする方法はないようです。配列に変換すればできるのでしょうけれど。HTMLCollectionのままforEachできたらすごく便利だったんですけどね…残念。

forEachはbreak、continueできない

forEachの引数はただのコールバック関数のため、for、for…in、for…ofなどのループ構文とは異なり、breakcontinueはできません。

const array = ['value1', 'value2', 'value3'];

array.forEach(function(elem, index) {
    console.log(index);
    if (1 < index) {
        break;              // SyntaxError: Illegal break statement
    }
    console.log(elem);
});

continueの代替としてreturnを使うことができます。

const array = ['value1', 'value2', 'value3'];

array.forEach(function(elem, index) {
    console.log(index);  // 3回実行される
    if (1 < index) {
        return;
    }
    console.log(elem);  // 2回実行される
});
0
value1
1
value2
2

breakの代替はなく、必ず全要素に対してコールバック関数が呼び出されます。

雑感

個人的にはループ処理ではインデックスを一緒に利用することが多いため、forEachが使いやすいなーと思います。HTMLCollectionのループは何が一番綺麗なのか結論が見えません…。DOM操作は全部NodeListで取得してしまえば解決?しかしHTMLCollectionにもメリットがあるのでそうもいかないのか…?

参考URL

for – JavaScript | MDN

for…in – JavaScript | MDN

for…of – JavaScript | MDN

Array.prototype.forEach() – JavaScript | MDN

タイトルとURLをコピーしました