日々の積み上げブログ

プログラミングを習得したい

2022/05/11(水)~05/17(火) JSPrimer(ループと反復処理)

同じ処理を繰り返したい時は、ループやイテレータなどを用いて、反復処理として同じ処理を繰り返し実行できます。

while文

while文は、条件式がtruthy(trueに変換)の時に処理を繰り返し、falsy(falseに変換)である場合は、何も実行せずwhile文を終了します。
条件式が最初からfalsyな値である場合には、while文の処理は1度も実行されません。

while (条件式) {
    実行する文; // 条件式がtruthyの場合に繰り返し処理されるコードを記述
}
  1. 条件式 の評価結果がtrueなら次のステップへ、falseなら終了
  2. 実行する文を実行
  3. ステップ1へ戻る

while文 使用例

let x = 0;
console.log(`ループ開始前のxの値: ${x}`);
while (x < 10) { // xが10になるまで繰り返し処理を実行(xの初期値が0なので、10回繰り返します)
    console.log(x);
    x += 1;  // 繰り返し処理の最後にxの値を1増やす 
}
console.log(`ループ終了後のxの値: ${x}`);

/* => ループ開始前のxの値: 0
0
1
2
3
4
5
6
7
8
9
ループ終了後のxの値: 10  */
  • xの値が10未満であるなら、コンソールへ繰り返しログが出力されます。
  • 繰り返し処理の最後でxの値を1ずつ増やし、xの値が10になった時に条件式がfalseと評価されるためwhile文が終了します。
  • 繰り返し処理の中で条件式がfalseとなるような処理を書かないと無限ループになってしまうため、それを防ぐためにxの値を1ずつ増やす処理が必要です。

無限ループ

条件式の評価結果が常にtrueと評価されると無限ループが起こります。無限ループが起こると、JavaScriptの繰り返し処理が無限に続いてしまいブラウザの画面がフリーズしてしまいます。
無限ループが発生した場合は、ブラウザのタブを閉じて、コードを修正した後、再度ブラウザを開くようにしましょう。

let i = 1;
// 条件式が常にtrueになるため、無限ループする
while (i > 0) {
    console.log(`${i}回目のループ`);
    i += 1;
}

do-while文

do-while文はwhile文と似ていますが、まず最初にdoブロック内の処理を実行し、その後while文で条件式の判定を行います。
while文と異なる部分は条件式の判定に関係なく、必ず1度はブロック内の処理が実行されるということです。

do {
    実行する文;  // 1番最初に実行される処理かつ条件式がtrueと評価された時に実行する処理
} while (条件式);
  1. 実行する文を実行
  2. 条件式 の評価結果がtrueなら次のステップへ、falseなら終了
  3. ステップ1へ戻る

do-while文 使用例

const x = 1000;
do {
    console.log(x); // => 1000
} while (x < 10);
  • 最初から条件式を満たさない場合でも、 初回の繰り返し処理が実行され、コンソールへ1000と出力されます。
  • 条件式x < 10はfalseと評価されるため、判定の部分で繰り返し処理を終了します。

for文

for (初期化式; 条件式; 増分式) {
    実行する文;
}
  1. 初期化式でループで使用する変数の宣言を行います。(繰り返し処理が始まる1番初めのみ実行されます。)
  2. ループ継続の条件式の評価結果がtrueである限りforブロック内の処理は繰り返し実行されます。falseと評価された瞬間に繰り返し処理は終了します。
  3. 増分式で繰り返し処理実行後にループに使う変数の更新処理が行われます。
  4. ステップ2へ戻る

for文 使用例

// 配列の要素の合計値を返す関数
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
  • numbers配列に含まれている要素を先頭から順番に変数totalへ加算することで合計値を計算しています。

配列のforEachメソッド(Arrayオブジェクトのメソッド)

JavaScriptの配列であるArrayオブジェクトには、反復処理のためのメソッドが備わっています。その一つがforEachメソッドです。

forEachメソッドは、配列の各要素に対する繰り返し処理をコールバック関数を利用して記述できるメソッドです。つまり、関数を引数として取ることができる高階関数であるということです。
具体的には、引数として与えられた関数を、配列の各要素に対して一度ずつ実行します。

配列.forEach( function(value, index, array){ // 引数にコールバック関数
  /*配列の各要素に対して実行される処理*/
});
  • コールバック関数の引数
    • valueには、配列の要素の値が1つずつ渡されてきます。
    • indexには、配列のインデックス番号が0から順番に渡されてきます。(省略可)
    • arrayには、配列自体が渡されます。(省略可)
  • 繰り返し処理は、配列の要素数分だけ実行されます。

forEach 使用例

// 配列の要素の合計値を返す関数
function sum(numbers) {
    let total = 0;
    numbers.forEach(num => {
        total += num;  // 配列の個々の要素に対して呼び出される処理(コールバック関数の処理)
    });
    return total;
}

sum([1, 2, 3, 4, 5]); // => 15
  • コールバック関数の引数numに配列の各要素が順番に入ってきます。
  • 繰り返し処理部分をfor文ではなくforEachメソッドを使って記述しています。
  • forEachメソッドは必ず配列のすべての要素を反復処理します。

break文

break文は処理中の文から抜けて次の文へ移行する制御文です。 while、do-while、forの中で使い、処理中のループを終了して次の処理へ移行します。

while (true) {
    処理A;
    break; // 繰り返し処理をここまでで終了するため、処理Bは実行されない
    処理B;
}
処理C; // 繰り返し処理の処理Aが実行された場合、その次は処理Cが実行される

break文 使用例①

// 引数の`num`が偶数ならtrueを返す関数
function isEven(num) {
    return num % 2 === 0;
}
// 引数の`numbers`に偶数が含まれているならtrueを返す
function isEvenIncluded(numbers) {
    // 偶数があるかどうかをtrueかfalseで表す変数(初期値false)
    let isEvenIncluded = false;
    for (let i = 0; i < numbers.length; i++) { // 要素数分判定を繰り返す
        const num = numbers[i]; // 1つずつ取得した要素の値を変数numへ代入
        if (isEven(num)) {  // 要素の値が偶数かどうかを判定し、
            isEvenIncluded = true;  // 偶数であった場合に変数isEvenIncludedにtrueを代入
            break;  // 配列内の要素に1つでも偶数があるかがわかればいいため、最初の偶数を見つけた時点で、for文を終了する
        }
    }
    return isEvenIncluded;
}
const array = [1, 5, 10, 15, 20];
console.log(isEvenIncluded(array)); // => true
  • 1つでも偶数があるかがわかればいいため、配列内から最初の偶数を見つけたらfor文での反復処理をbreak文で終了します。
  • break文の記述は、現在の関数を終了させることができるreturnを使って記述することもできます。
    • break; => return true;
    • return isEvenIncluded; => return false

上記のコードをfor文を使わず配列のメソッドを使って簡潔に書いたバージョン

const numbers = [1, 5, 10, 15, 20];

// 配列の要素に1つでも偶数を含んでいるかを判定する関数
const includeEvenNumber2 = (array) => { 
    return array.some(num => num % 2 === 0);
};

// 関数呼び出し
console.log(includeEvenNumber2(numbers)); // => true
  • someメソッドは、配列の少なくとも一つの要素が条件に一致すればtrueを返す配列メソッドです。MDN-Array.prototype.some()

配列のsomeメソッド(Arrayオブジェクトのメソッド)

someメソッドは、配列の少なくとも一つの要素が条件に一致すればtrueを返す配列メソッドです。
someメソッドでは、配列の各要素をテストする処理をコールバック関数として受け取ります。条件となるコールバック関数が、一度でもtrueを返した時点で反復処理を終了し、最後にtrueを返します。

continue文

continue文は現在の反復処理を終了して、次の反復処理を行います。 continue文は、while、do-while、forの繰り返し処理の中で使うことができます。

画像参照

違い
break 繰り返し処理全体を終了して、次の処理へ
continue 現在の繰り返し処理を終了して、次の繰り返し処理を実行

continue文 使用例

// 配列の各要素が偶数ならtrueを返す関数
function isEven(num) {
    return num % 2 === 0;
}
// 配列の要素の内、偶数のものだけを取得する関数
function filterEven(numbers) {
    const results = []; // 偶数の要素を入れるための空の配列を作成
    for (let i = 0; i < numbers.length; i++) { // 配列の要素数分繰り返し処理をする
        const num = numbers[i];  // 順番に取得した要素の値を変数numに代入
        // 偶数ではないなら、次のループへ
        if (!isEven(num)) {
            continue; // 変数numに代入した要素が偶数でないと分かった時点で現在のループ処理を終了し、次の要素のループ処理に移行する
        }
        // 偶数を`results`に追加
        results.push(num); // 要素の値が偶数だと判定できたので、新しい配列resultsに要素numを追加する
    }
    return results;
}
const array = [1, 5, 10, 15, 20];
// 関数呼び出して実行
console.log(filterEven(array)); // => [10, 20]
  • 配列の中から偶数を集め、新しい配列を作り返しています。
  • 配列の要素が偶数ではない場合、処理途中のfor文をcontinue文でスキップしています。

配列のfilterメソッド

filterメソッドは、配列から特定の値だけを集めた新しい配列を作ることができます。
filterメソッドは、配列の各要素を順番にコールバック関数の条件と照らし合わせ、条件に当てはまる要素のみを保持する新しい配列を作成します。

let newArray = arr.filter(callback(element[, index, [array]])[, thisArg])
  • callback: 配列の各要素に対して実行するテスト関数です。この関数が true を返した要素は残され、false を返した要素は取り除かれます。
  • element: 配列内で処理中の現在の要素です。順番に1つずつ渡されます。
  • index(省略可): 配列内で処理中の現在の要素のインデックス番号です。順番に渡されます。
  • array(省略可): 配列自体です。

filterメソッドを使った使用例 偶数のみを保持する配列を作成します。

const array = [1, 2, 3, 4, 5, 6, 7, 8];

const filteredArray = array.filter((currentValue, index, array) => {
    // 偶数ならtrue、そうでないならfalseを返す
    // trueを返した要素のみの配列を新しく作成
    return currentValue % 2 === 0;
});

console.log(filteredArray); // => [ 2, 4, 6, 8 ]

for...in文

for...in文はオブジェクトのプロパティに対して、反復処理を行います。オブジェクトにfor...in文を使うと、オブジェクトのプロパティの数分だけ繰り返し処理が行われます。

for (const key in obj) {
    オブジェクトの各プロパティに対する繰り返し処理;
}
  • key: キーを使って、オブジェクトのプロパティを1つずつ取得します。
  • obj: オブジェクトや配列を設定できます。

for...in文の使用例

// 反復処理に使うオブジェクト
const obj = {
    "a": 1,
    "b": 2,
    "c": 3
};
// 注記: ループのたびに毎回新しいブロックに変数keyが定義されるため、再定義エラーが発生しない
for (const key in obj) { // objオブジェクトからkey名で各プロパティを取得し、変数keyに代入
    // オブジェクトの各プロパティに対する繰り返し処理
    const value = obj[key];
    console.log(`key:${key}, value:${value}`);
}
//=> "key:a, value:1"
//=> "key:b, value:2"
//=> "key:c, value:3"
  • objのプロパティ名をkey変数に代入してプロパティの値の分だけ反復処理を行います。
  • objには、3つのプロパティ名があるため繰り返しが3回行われます。
  • constで定義している変数keyはループのたびに毎回新しいブロックを作成し新たに定義されているため、再定義エラーになることはありません。

for...in文の問題点

JavaScriptでは、オブジェクトは何らかのオブジェクトを継承しています。 for...in文は、対象となるオブジェクトのプロパティを列挙する場合に、親オブジェクトまで列挙可能なものがあるかを探索して列挙します。 そのため、オブジェクト自身が持っていないプロパティも列挙されてしまい、意図しない結果になる場合があります。

安全にオブジェクトのプロパティを列挙するには、Object.keysメソッド、Object.valuesメソッド、Object.entriesメソッドなどが利用できます。

また、配列に対してもfor...in文を使うべきではありません。なぜなら、for...in文が列挙する配列オブジェクトのプロパティ名は、要素の値ではなく、要素のインデックスを文字列化した値だからです。配列の要素をループ処理させたいときは、for...of文for文または、forEachメソッドを使うようにしましょう。

  • Object.keys(): 引数で渡されたオブジェクト自身が持つ列挙可能なプロパティ名の配列を返すメソッドです。

  • Object.values(): 引数で渡されたオブジェクトが持つ列挙可能なプロパティの値配列にして返します。

  • Object.entries(): : 引数で渡されたオブジェクトが持つ列挙可能なプロパティ名とプロパティ値のペア[key, value]の形式の配列として返します。

for...in文の使用例をfor...inを使わないで記述した例

const obj = {
    "a": 1,
    "b": 2,
    "c": 3
};
// Object.keysメソッドとforEachメソッドの組み合わせバージョン
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...ofメソッドとObject .entriesメソッドの組み合わせバージョン
for(const [key,  value] of Object.entries(obj)){ // [key,  value]の部分は分割代入を行う
    console.log(`key:${key}, value:${value}`);
};
//=> "key:a, value:1"
//=> "key:b, value:2"
//=> "key:c, value:3"
  • Object.keysメソッドforEachメソッドの組み合わせバージョン
    • Object.keysメソッドを使い、objオブジェクトのプロパティ名配列を取得します。
    • forEachメソッドで取得したプロパティ名を1つずつ順番に使い、繰り返し処理を行います。
  • for...ofメソッドObject .entriesメソッドの組み合わせバージョン
    • Object.entriesメソッドでobjのプロパティ名と値のペアで取得した配列をそれぞれkeyとvalue変数に分割代入します。
    • keyにはプロパティ名、valueにはプロパティの値が入ります。

[ES2015] for...of文

for...of文は、反復可能オブジェクト(iterableオブジェクト)に対して繰り返し処理を実行できます。代表的な反復可能オブジェクトとは、配列(Array)やMap、Set、Stringオブジェクトなどです。

for ( const value of iterable ){
  // 反復可能オブジェクトの各要素に対する繰り返し処理
}
  • value : 反復処理の各回において、異なるプロパティの値が value に割り当てられます。
  • iterable : 反復可能オブジェクト。(配列、Map、Set、String、argumentsなど)

for...of文の使用例

const array = [1, 2, 3];
for (const value of array) {  // 配列の各要素を繰り返し処理
    console.log(value);
}
//=> 1
//=> 2
//=> 3
  • 配列arrayの要素を1つずつ取得して、value変数に代入したものを繰り返し処理しています。
  • 配列arrayの要素が3つあるので3回繰り返しが実行されます。

反復可能オブジェクト(iterableオブジェクト)とは?

Symbol.iteratorという特別な名前のメソッドを実装したオブジェクトをiterableと呼びます。簡単にまとめると、反復処理時の動作が定義されたオブジェクトがiterableオブジェクトです。また、iterableオブジェクトは反復処理時に次の返す値を定義しています。

for...in文とfor...of文の使い分け

名前 繰り返し取得できる値 繰り返し処理するべきもの  説明
for...in文 key(プロパティ名、インデックス番号) オブジェクトのループ オブジェクトのすべての列挙可能なプロパティに対して、順序不定で繰り返し処理を実行
for...of文 value(keyに対応する値) 反復可能オブジェクトのループ(配列、Map、Set、String、argumentsなど) 反復可能なオブジェクトが定義した順序で値を反復処理

reduce メソッド

reduceメソッドは2つずつ要素を取り出し(左から右へ)、その値をコールバック関数に適用し、 次の値として1つの値を返します。 最終的な、reduceメソッドの返り値は、コールバック関数が最後にreturnした値となります。

const result = array.reduce((前回の値, 現在の値) => {
    return 次の値;
}, 初期値);

以下の例では、配列のすべての要素の和を返すsumWithInitial関数を定義しています。

const array1 = [1, 2, 3, 4];

// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = (array) => array.reduce(
  (previousValue, currentValue) => previousValue + currentValue,
  initialValue
);

console.log(sumWithInitial(array1));  //=> 10

参考

JSPrimer-ループと反復処理

独習JavaScript

MDN-Array.prototype.forEach()

MDN-Array.prototype.some()