Tips

JavaScriptのfor文やif文で使われているreturn、breakは何をしているのか?

  • このエントリーをはてなブックマークに追加

「条件分岐が何かは分かるけど、自分でゼロから作るのは難しい」
「ネストはダメ!とか言われるけど、複雑な条件のときにネストするしかないと思っている」
「breakが何をしているのかよく分からず使っている」

本日はそんな方に向けたJavaScriptにおける繰り返し処理や条件分岐についての便利テクニックを紹介します。

for文やif文は初心者がプログラムの流れを理解するために紹介されるメインの構文です。

その中で使用される「return」と「break」というキーワードは、詳しく習わないですが実は重要な役割を果たしています。

今回は「return」と「break」がそれぞれどのような機能を持つのかについて探求してみます。

また動画もあるので必要に応じて使ってください。

JavaScriptでreturnがある理由

まだreturnのこと理解しきれていない初心者の方もいるかと思いますので、簡単にreturnの仕組みだけお話します。

returnという文法はもともとは関数のなかで使うことが多いです。

例えば以下のように数字の足し算をする関数を作り実行すると「undefined」がコンソールに表示されます。

function sumNum() {
  let a = 10;
  let b = 10;
  a + b;
}
console.log(sumNum()); // undefinedになる

「20じゃないの?」と思われた方がいるかもしれません。

確かにa + b = 20なのですが関数というものは必ず結果を返すわけではありません。

returnをつけた場合でのみ結果を返すことになっています、つまりreturnは必須ではないのです。

function sumNum() {
  let a = 10;
  let b = 10;
  // returnを追加した
  return a + b;
}
console.log(sumNum()); // 20になる

初心者の方はまず、関数は必ずしも結果を返すわけでもなく結果が欲しい時はreturnを書くということを知ってください。

またreturnのルールとしては「returnの後続の処理は実行されない」というものがあります。

例えば、先ほどの関数でreturnの後続にconsole.log(“hello”);を書いても実行されません。

function sumNum() {
  let a = 10;
  let b = 10;
  return a + b;
  // ここは実行されない
  console.log("hello");
}
console.log(sumNum());
function sumNum() {
  let a = 10;
  let b = 10;
  // ここに書くと実行される
  console.log("hello");
  return a + b;
}
console.log(sumNum());

関数を作るときに処理はreturnに辿り着くまでに全て書いておく必要があります。

慣れてくると当たり前に感じるのですが初心者のうちはミスすることがあるので注意してください。

JavaScriptの条件分岐でifの中でreturnがあるのはなぜか?

ググったり他人のコードを見ていると以下のような書き方を見かけたことはないでしょうか?

if (nubmer == null) return console.log("null");

コードの意味は一旦置いといて、ifの中でreturnがありますね。

returnは関数で初めて知る人が多いと思いますが、なぜifの中でreturnを使っているんでしょうか?

後で例題をもって詳しく解説しますが、先にザックリ答えを言うと「elseを省略」しています。

ifとセットでelseも覚えますが、elseを省略するケースとはどんな状況なのか見ていきましょう。

JavaScriptの条件分岐で本当にelseが必要か問題

まず以下のコードを作ってみました。

const number = {
  id: null,
};

function ifFunc(number) {
  if (number?.id != null) {
    if (number.id < 50) {
      console.log("50より小さい");
    } else if (number.id < 100) {
      console.log("100より小さい");
    } else {
      console.log("101より大きい");
    }
  } else {
    console.log("数字ではありません");
  }
}

ifFunc(number); // "数字ではありません"と出力される

numberというオブジェクトがあり、idというプロパティが格納されています。

idに何か値が入る想定で、値によって条件分岐させるような内容になっています。

上記のコードでは一旦nullを入れているので、外側の条件でelseをたどって「数字ではありません」となります。

numberのidを違う値に変えてみましょう。

const number = {
  id: 10,
};

function ifFunc(number) {
  if (number?.id != null) {
    if (number.id < 50) {
      console.log("50より小さい");
    } else if (number.id < 100) {
      console.log("100より小さい");
    } else {
      console.log("101より大きい");
    }
  } else {
    console.log("数字ではありません");
  }
}

ifFunc(number); // "50より小さい"と出力される

10を入れたので、ネストされた内側の条件分岐に入っていき「50より小さい」となりました。

もう少しやってみましょう。

const number = {
  id: 80,
};

function ifFunc(number) {
  if (number?.id != null) {
    if (number.id < 50) {
      console.log("50より小さい");
    } else if (number.id < 100) {
      console.log("100より小さい");
    } else {
      console.log("101より大きい");
    }
  } else {
    console.log("数字ではありません");
  }
}

ifFunc(number); // "100より小さい"と出力される

今度は80を入れたので、ネストされた条件分岐に入りますが最初のif文には沿わないのでelse ifの方の「100より小さい」を出力しました。

ざっくりコードの全体像が分かってきたかと思います。

正直このままで何の問題もないのですが、あえて同じ動作を別の書き方で作ってみたいと思います。

const number = {
  id: null,
};

function ifFuncReturn(number) {
  if (number?.id == null) return console.log("数字ではありません");

   if (number.id < 50) {
     console.log("50より小さい");
   } else if (number.id < 100) {
     console.log("100より小さい");
   } else {
     console.log("101より大きい");
  }

}

ifFuncReturn(number); // "数字ではありません"と出力される

冒頭でお話したreturnがif文のなかにありますね。

こちらの書き方でも先ほどと同じ動作をします。

「if (number?.id == null) return console.log(“数字ではありません”);」については、「idがnullだったら”数字ではありません”と出力して終わる」という意味になります。

先ほどのコードで言うと外側のelse文の役割をしています。

ググって出てきた記事などでif文のなかでreturnを使っているのを見かけたら、「elseみたいなことしてるんだな」と思ってもらえたら良いでしょう。

先に言っておくと「elseではなくreturnで書くべき」ということではありません。

それぞれの書き方にはメリット、デメリットがあるので状況によって使い分けるのがベストです。

ではなぜif文のなかでreturnを書く人がいるのでしょうか?

大きな理由としては、

・複雑なネストを少しでも見やすくしたい
・処理を少しでも早くしたい

などです。

複雑なネストを少しでも見やすくしたい

主観もありますが、returnを使うことで少しは複雑な条件分岐が見やすくなります。

ちなみに複雑なネストを緩和するには「そもそもの条件を見直す」ということも考える癖を付けておくことをおススメします。

// returnを使わない場合
function ifFunc(number) {
  if (number?.id != null) {
    if (number.id < 50) {
      console.log("50より小さい");
    } else if (number.id < 100) {
      console.log("100より小さい");
    } else {
      console.log("101より大きい");
    }
  } else {
    console.log("数字ではありません");
  }
}

// returnを使う場合
function ifFuncReturn(number) {
  if (number?.id == null) return console.log("数字ではありません");

   if (number.id < 50) {
     console.log("50より小さい");
   } else if (number.id < 100) {
     console.log("100より小さい");
   } else {
     console.log("101より大きい");
  }

}

少しでも処理を早くしたい

実は先ほどの2例では少しだけ処理の方法が変わっているんです。

最初に紹介した下記の場合は「総当たり検証」といって、全ての条件を網羅的に見たうえで適した条件に入り込んでいます。

// すべての条件を1周して見るような処理をしている
function ifFunc(number) {
 // この条件を確認
  if (number?.id != null) {
  // この条件も確認
    if (number.id < 50) {
      console.log("50より小さい");
  // この条件も確認
    } else if (number.id < 100) {
      console.log("100より小さい");
  // この条件も確認
    } else {
      console.log("101より大きい");
    }
 // この条件も確認
  } else {
    console.log("数字ではありません");
  }
}

そして一部をreturnに置き換えた以下の書き方だと、idがnullだった場合は”数字ではありません”を出力して後続の条件は見ずに全体の処理を完了としています。

function ifFuncReturn(number) {
  if (number?.id == null) return console.log("数字ではありません");
  // ↑の条件にヒットした段階で以下の処理は見ないで終了する
   if (number.id < 50) {
     console.log("50より小さい");
   } else if (number.id < 100) {
     console.log("100より小さい");
   } else {
     console.log("101より大きい");
  }

}

正直なところ速度の違いと言ってもユーザー側で分かるような時間差はないですが、処理の内容や開発者の方針によっては「returnを使って少しでも早くしたい」という事情があったりします。

returnが混乱の種になる可能性

ちなみに先ほどのreturnを使った書き方をする人がいる一方で、「returnを書いたり書かなかったりしているのは煩雑なのでは?」という考えの方もいます。

冒頭でお話したようにreturnは関数でも登場します。

さらに処理の内容によってはreturnを省略することもできます。

コードを書いた本人は理解できていたとしても、別の開発者が引き継いでコードを確認した際に「returnがあったり無かったりするのは何か意味があるんだろうか?」と少し困惑する可能性があるんですね。

つまりreturnは使い勝手が良いが故に「書き手によって煩雑になる」という懸念点も一部では言われています。

一番簡単な対策としては以下のように「省略できるreturnを省略しない」という方法です。

function ifFuncReturn(number) {
 // 大枠の条件に外れたら処理自体を終了する
  if (number?.id == null) return console.log("数字ではありません");
 // 戻り値がなくてもreturnは省略しない
  if (number.id < 50) {
    console.log("50より小さい");
    return;
  }
 // 戻り値がなくてもreturnは省略しない
  if (number.id < 100) {
    console.log("100より小さい");
    return;
  }
  //全てのifに合わなかった時のデフォルトの処理を最後に書いておく
  console.log("101より大きい");
}

ネストされていた各条件を小分けにしたような構成にして、各条件について戻り値があっても無くてもreturnで終わるようにしています。

またネストされていた内側のelse文については、if文ではなく「最後に書く」ことをしていてswitch文で言うデフォルト処理のような扱いになっています。

コードの長さは変わらないですが、elseを省略した書き方ですね。

JavaScriptにおける条件分岐の書き方を3パターン紹介しましたが、自分としては作る内容によって使い勝手が変わってくると考えています。

プログラムには正解が無いので同じ処理でも人によって書き方が違うので、他人のコードを見たときにパッとくみ取れるようになるためにも今回の件は知っておくと良いかもしれません。

繰り返し処理や条件分岐で使われるbreakは何をしているのか?

最後にreturnと同じようにコードの簡略化を目的に使われるbreakについても紹介します。

ググった時の記事や他人のコードで以下のようなものを見たことがあると思います。

const arr = ["a", "a", "b", "a", "a", "a"];
let data;
for (let i = 0; i < arr.length; i++) {
  console.count(arr[i]);
  if (arr[i] === "b") {
    data = "bを見つけた!";
    break;
  }
}

プログラミングスクールや書籍ではfor文やif文の説明がメインになっていて、breakについてはあまり説明されないことがあります。

breakもreturnと同じように「特定の条件になったら処理を終了させる」ために使います。

例えば上記のコードでは配列arrにはaとbが並んでいます。

bを見つけたらアラートを出したいとした場合、breakを使うとbを見つけた時点で繰り返しを終了させることになります。

仮にbreakがなかったとしても動作自体は問題ないですが、内部では配列arrの中にある要素を全て調べることになります。

簡単なプログラムであれば特に問題ないですが、複雑なプログラムや扱うデータが膨大なAPI処理だと処理時間が変わってくることが考えられます。

最初に紹介したreturnに加えてbreakについても使い方を知っておきましょう。

また今回の記事は以下の本を参考にして作成しましたので興味のある方はどうぞ

今回の記事を作成するのに参考にした本はこちらから

  • このエントリーをはてなブックマークに追加