# ループと反復処理 jsで出てくるwhile文やfor文などの基本的な反復処理と制御文 プログラミングにおいて繰り返す為の同じコードを何度も書く必要はない。 ループ処理等をを使って同じ処理を繰り返すことができる。 繰り返しの記載をやらないと... ```javascript console.log('hello #1') //hello #1 console.log('hello #2') //hello #2 console.log('hello #3') //hello #3 console.log('hello #4') //hello #4 console.log('hello #5') //hello #5 console.log('hello #6') //hello #6 console.log('hello #7') //hello #7 console.log('hello #8') //hello #8 console.log('hello #9') //hello #9 console.log('hello #10') //hello #10 ``` 繰り返しを使うと... ```javascript let num = 1 while (num < 11) { console.log(`hello ${num}回目`) num++ } //hello 1回目 //hello 2回目 //hello 3回目 //hello 4回目 //hello 6回目 //hello 7回目 //hello 8回目 //hello 9回目 //hello 10回目 ``` ## while文 条件式がtrueのであれば反復処理を行う。 ```javascript while (条件式) { 実行する文; } ``` 実行の流れ 1. 条件式 の評価結果がtrueなら次のステップへ、falseなら終了 1. 実行する文を実行 1. ステップ1へ戻る 条件式がfalseの場合は何も実行されずwhile文が終了する。 以下のコードは実行されない... ```javascript while (false) { console.log('hello') } ``` xの値が10未満ならコンソールが出力される。 ```javascript let x = 0; console.log(`ループ開始前のxの値: ${x}`); while (x < 10) { console.log(x); x += 1; } console.log(`ループ終了後のxの値: ${x}`); ``` **無限ループ** 繰り返し処理を書いてる時に、文中に書き間違いや条件式の記載ミスをしてしまうと無限ループになる。 ```javascript let i = 1; // 条件式が常にtrueになるため、無限ループする while (i > 0) { console.log(`${i}回目のループ`); i += 1; } ``` **無限ループになった場合...** ブラウザ環境 - ブラウザそのものを閉じることで強制的に停止する。 Node環境 - Ctrl + Cを入力し、終了シグナルを送ることで強制的に停止する。 ## do-while文 ```javascript do { 実行する文; } while (条件式); ``` 実行の流れ 1. 実行する文を実行 1. 条件式 の評価結果がtrueなら次のステップへ、falseなら終了 1. ステップ1へ戻る whileと異なり、最初の実行は必ず走る。 ```javascript const x = 1000; do { console.log(x); // => 1000 } while (x < 10); ``` ## for文 繰り返す範囲を指定した反復処理を書くことができる。 ```javascript for (初期化式; 条件式; 増分式) { 実行する文; } ``` 1. 初期化式 で変数の宣言 1. 条件式 の評価結果がtrueなら次のステップへ、falseなら終了 1. 実行する文 を実行 1. 増分式 で変数を更新 ```javascript let total = 0; // totalの初期値は0 // for文の実行フロー // iを0で初期化 // iが10未満(条件式を満たす)ならfor文の処理を実行 // iに1を足し、再び条件式の判定へ for (let i = 0; i < 10; i++) { total += i + 1; // 1から10の値をtotalに加算している } console.log(total); // => 55 ``` for文の実用的な使い方 ```javascript function sum(numbers) { let total = 0; for (let i = 0; i < numbers.length; i++) { total += numbers[i]; } return total; } console.log(sum([1, 2, 3, 4, 5])); // => 15 ``` jsのArray[配列]オブジェクトには、反復処理のためのメソッドが備わっている ## 配列のforEachメソッド ```javascript const array = [1, 2, 3]; array.forEach(currentValue => { // 配列の要素ごとに呼び出される処理 }); ``` jsでは、関数がファーストクラスであるため、 その場で作った匿名関数(名前のない関数)を引数として渡せる。 引数として渡される関数のことを**コールバック関数**と呼ぶ また、コールバック関数を引数として受け取る関数やメソッドのことを高階関数と呼びます。 ```javascript const array = [1, 2, 3]; // forEachは"コールバック関数"を受け取る高階関数 array.forEach(コールバック関数); ``` forEachメソッドのコールバック関数は、 配列の要素が先頭から順番に渡されて実行される。 コールバック関数の仮引数であるcurrentValueには 1から3の値が順番に渡される。 ```javascript アロー関数の const array = [1, 2, 3]; array.forEach(currentValue => { console.log(currentValue); }); // 1 // 2 // 3 // と順番に出力される アロー関数を使わない場合 const array = [1, 2, 3]; array.forEach(function (currentValue) { console.log(currentValue); }); ``` 先ほどのfor文の例と比較して見ると... ```javascript function sum(numbers) { let total = 0; numbers.forEach(num => { total += num; }); return total; } sum([1, 2, 3, 4, 5]); // => 15 ``` forEachはfor文の条件式はない。 必ず配列のすべての要素を反復処理を行う 変数iといった一時的な値を定義する必要がないため for分に比べてシンプルに反復処理を書ける。 ## break文 break文は処理中の文から抜けて次の文へ移行する制御文 while、do-while、forの中で使い、処理中のループを抜けて 次の文へ制御を移せる。 ```javascript while (true) { break; // *1 へ } // *1 次の文 //強制終了もできる。 let x = 0; console.log(`ループ開始前のxの値: ${x}`); while (x < 10) { console.log(x); x += 1; break } console.log(`ループ終了後のxの値: ${x}`); //ループ開始前のxの値: 0 //0 //ループ終了後のxの値: 1 ``` switch文で出てきたものと同様で、処理中のループ文を終了できる。 次のコードでは配列の要素に1つでも偶数を含んでいるかを判定 ```javascript const numbers = [1, 5, 10, 15, 20]; // 偶数があるかどうか let isEvenIncluded = false; for (let i = 0; i < numbers.length; i++) { const num = numbers[i]; // numが2で割り切れるなら偶数 if (num % 2 === 0) { isEvenIncluded = true; break; } } console.log(isEvenIncluded); // => true ``` 1つでも偶数があるかがわかればいいため、 配列内から最初の偶数を見つけたらfor文での反復処理を終了する。 このような処理は、使い回せるように関数として実装するのが一般的 同様の処理を行う関数 次のコードでは、break文が実行され、ループを抜けた後にreturn文で結果を返す。 ```javascript // 引数の`num`が偶数ならtrueを返す function isEven(num) { return num % 2 === 0; } // 引数の`numbers`に偶数が含まれているならtrueを返す function isEvenIncluded(numbers) { let isEvenIncluded = false; for (let i = 0; i < numbers.length; i++) { const num = numbers[i]; if (isEven(num)) { isEvenIncluded = true; break; } } return isEvenIncluded; } const array = [1, 5, 10, 15, 20]; console.log(isEvenIncluded(array)); // => true ``` return文は現在の関数を終了させることができるため、 次のように書くこともできる。 numbersに1つでも偶数が含まれていれば結果はtrueとなるため、 偶数の値が見つかった時点でtrueを返す。 ```javascript function isEven(num) { return num % 2 === 0; } function isEvenIncluded(numbers) { for (let i = 0; i < numbers.length; i++) { const num = numbers[i]; if (isEven(num)) { return true; } } return false; } const numbers = [1, 5, 10, 15, 20]; console.log(isEvenIncluded(numbers)); // => true ``` ## 配列のsomeメソッド 先ほどのisEvenIncluded関数は、偶数を見つけたら true を返す関数 配列ではsomeメソッドで同様のことができる。 someメソッドは、配列の各要素をテストする処理をコールバック関数として受け取る。 コールバック関数が、一度でもtrueを返した時点で反復処理を終了し、someメソッドはtrueを返す。 ```javascript const array = [1, 2, 3, 4, 5]; const isPassed = array.some(currentValue => { // テストをパスするとtrue、そうでないならfalseを返す }); ``` someメソッドを使うことで、配列に偶数が含まれているかは次のように書くことができます。 受け取った値が偶数であるかをテストするコールバック関数としてisEven関数を渡します。 ```javascript function isEven(num) { return num % 2 === 0; } const numbers = [1, 5, 10, 15, 20]; console.log(numbers.some(isEven)); // => true ``` ## continue文 continue文は現在の反復処理を終了して、次の反復処理を行う。 continue文は、while、do-while、forの中で使うことができる たとえば、while文の処理中でcontinue文が実行されると、現在の反復処理はその時点で終了されます。 そして、次の反復処理で条件式を評価するところからループが再開されます。 ```javascript while (条件式) { // 実行される処理 continue; // `条件式` へ // これ以降の行は実行されません } ``` 次のコードでは、配列の中から偶数を集め、新しい配列を作り返しています。 偶数ではない場合、処理中のfor文をスキップしています。 ```javascript // `number`が偶数ならtrueを返す function isEven(num) { return num % 2 === 0; } // `numbers`に含まれている偶数だけを取り出す function filterEven(numbers) { const results = []; for (let i = 0; i < numbers.length; i++) { const num = numbers[i]; // 偶数ではないなら、次のループへ if (!isEven(num)) { continue; } // 偶数を`results`に追加 results.push(num); } return results; } const array = [1, 5, 10, 15, 20]; console.log(filterEven(array)); // => [10, 20] ``` もちろん、次のようにcontinue文を使わずに「偶数ならresultsへ追加する」 という書き方も可能です。 ```javascript // `number`が偶数ならtrueを返す function isEven(num) { return num % 2 === 0; } // `numbers`に含まれている偶数だけを取り出す function filterEven(numbers) { const results = []; for (let i = 0; i < numbers.length; i++) { const num = numbers[i]; // 偶数ではないなら、次のループへ if (isEven(num)) { results.push(num); } // 偶数を`results`に追加 } return results; } const array = [1, 5, 10, 15, 20]; console.log(filterEven(array)); // => [10, 20] ``` この場合、条件が複雑になってきた場合にネストが深くなってコードが読みにくくなります。 そのため、ネストしたif文のうるう年の例でも紹介したように、 できるだけ早い段階でそれ以上処理を続けない宣言をすることで、 複雑なコードになることを避けています。 ## 配列のfilterメソッド 配列から特定の値だけを集めた新しい配列を作るにはfilterメソッドを利用できます。 filterメソッドには、配列の各要素をテストする処理をコールバック関数として渡します。 コールバック関数がtrueを返した要素のみを集めた新しい配列を返します。 ```javascript const numbers = [1, 2, 3, 4, 5]; // テストをパスしたものを集めた配列 const filteredArray = numbers.filter((currentValue, index, array) => { // テストをパスするならtrue、そうでないならfalseを返す }); //currentValue = 配列の中身の値 //index = index //array = 配列(numbers)配列そのもの const array = [1, 5, 10, 15, 20]; const value = array.filter((num, index, array) => { return array[index] //どっちも変わらない return array }) console.log(value) //[ 1, 5, 10, 15, 20 ] ``` 先ほどのcontinue文を使った値の絞り込みはfilterメソッドを使うとより簡潔に書けます。 次のコードでは、filterメソッドを使って偶数だけに絞り込んでいます。 ```javascript function isEven(num) { return num % 2 === 0; } const array = [1, 5, 10, 15, 20]; console.log(array.filter(isEven)); // => [10, 20] ``` ## for...in文 for...in文はオブジェクトのプロパティに対して、反復処理を行います。 ```javascript for (プロパティ in オブジェクト) { 実行する文; } ``` 次のコードではobjのプロパティ名をkey変数に代入して反復処理をしています。 objには、3つのプロパティ名があるため3回繰り返されます ループのたびに毎回新しいブロックを作成しているため、ループごとに定義する変数keyは再定義エラーになりません。詳細は「関数とスコープ」の章の「ブロックスコープ」で解説します)。 ```javascript const obj = { "a": 1, "b": 2, "c": 3 }; // 注記: ループのたびに毎回新しいブロックに変数keyが定義されるため、再定義エラーが発生しない //1回目の処理だと... for (const key in obj) { //"a" const value = obj[key]; //value = obj["a"] //valueの値は1になる //value = obj.key この形だとvalueの値がundefinedになる。 console.log(`key:${key}, value:${value}`); } // "key:a, value:1" // "key:b, value:2" // "key:c, value:3" ``` オブジェクトに対する反復処理のためにfor...in文は有用に見えますが、 多くの問題を持っています。 javaScriptでは、オブジェクトは何らかのオブジェクトを継承しています。 for...in文は、対象となるオブジェクトのプロパティを列挙する場合に、 親オブジェクトまで列挙可能なものがあるかを探索して列挙します。 そのため、オブジェクト自身が持っていないプロパティも列挙されてしまい、 意図しない結果になる場合があります。 安全にオブジェクトのプロパティを列挙するには、 Object.keysメソッド、 Object.valuesメソッド、 Object.entriesメソッド などが利用できます。 先ほどの例である、 オブジェクトのキーと値を列挙するコードはfor...in文を使わずに書けます。 Object.keysメソッドは引数のオブジェクト自身が持つ列挙可能なプロパティ名の配列を返します。 そのためfor...in文とは違い、親オブジェクトのプロパティは列挙されません。 ```javascript const obj = { "a": 1, "b": 2, "c": 3 }; Object.keys(obj).forEach(key => { const value = obj[key]; console.log(`key:${key}, value:${value}`); }); // "key:a, value:1" // "key:b, value:2" // "key:c, value:3" ``` また、for...in文は配列に対しても利用できますが、 こちらも期待した結果にはなりません。 次のコードでは、配列の要素が列挙されそうですが、実際には配列のプロパティ名が列挙されます。 for...in文が列挙する配列オブジェクトのプロパティ名は、要素のインデックスを文字列化した "0"、"1"となるため、その文字列がnumへと順番に代入されます。 そのため、数値と文字列の加算が行われ、意図した結果にはなりません。 ```javascript const numbers = [5, 10]; let total = 0; for (const num in numbers) { // 0 + "0" + "1" という文字列結合が行われる total += num; } console.log(total); // => "001" ``` ## for...of文 最後にfor...of文についてです。 JavaScriptでは、Symbol.iteratorという特別な名前のメソッドを実装したオブジェクトをiterableと呼びます。 iterableオブジェクトは、for...of文で反復処理できます。 iterableについてはgeneratorと密接な関係がありますが、 ここでは反復処理時の動作が定義されたオブジェクトと認識していれば問題ありません。 iterableオブジェクトは反復処理時に次の返す値を定義しています。 それに対して、for...of文では、iterableから値を1つ取り出し、 variableに代入して反復処理を行います。 ```javascript for (variable of iterable) { 実行する文; } ``` 実はすでにiterableオブジェクトは登場していて、 Arrayはiterableオブジェクトです。 次のようにfor...of文で、配列から値を取り出して反復処理を行えます。 for...in文とは異なり、インデックス値ではなく配列の値を列挙します。 ```javascript const array = [1, 2, 3]; for (const value of array) { console.log(value); } // 1 // 2 // 3 ``` JavaScriptではStringオブジェクトもiterableです。 そのため、文字列を1文字ずつ列挙できます。 ```javascript const str = "𠮷野家"; for (const value of str) { console.log(value); } // "𠮷" // "野" // "家" ``` そのほかにも、TypedArray、Map、Set、DOM NodeListなど、 Symbol.iteratorが実装されているオブジェクトは多いです。 for...of文は、それらのiterableオブジェクトで反復処理に利用できます。 ## まとめ この章では、for文などの構文での反復処理と配列のメソッドを使った反復処理について比較しながら見ていきました。 for文などの構文ではcontinue文やbreak文が利用できますが、配列のメソッドではそれらは利用できません。 一方で配列のメソッドは、一時的な変数を管理する必要がないことや、処理をコールバック関数として書くという違いがあります。 どちらの方法も反復処理においてはよく利用されます。 どちらが優れているというわけでもないため、 どちらの方法も使いこなせるようになることが重要です。