ループの方法が多すぎるし、それぞれちょっとずつ違いがあって全然覚えられないためまとめておきます。
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
hasOwnProperty
はObject.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でインデックスがほしいとき
配列の場合は以下のようにArray
のentries
を利用するとインデックスと値のペアをループすることができます。が、後ほど紹介する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);
}
NodeList
のentries
は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などのループ構文とは異なり、break
やcontinue
はできません。
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
にもメリットがあるのでそうもいかないのか…?