JavaScriptにおけるgoto文の代替方法14選

JavaScriptでgoto文を使わずに同様の処理を実現する方法JS
この記事は約26分で読めます。

※本記事のコンテンツは、利用目的を問わずご活用いただけます。実務経験10000時間以上のエンジニアが監修しており、基礎知識があれば初心者にも理解していただけるように、常に解説内容のわかりやすさや記事の品質に注力しております。不具合・分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。(理解できない部分などの個別相談も無償で承っております)
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●goto文とは

プログラミングにおいて、「goto文」という言葉を聞いたことがある方も多いのではないでしょうか。

goto文は、プログラムの実行位置を任意の場所に移動させる制御文の一種です。

一見すると便利そうに思えるgoto文ですが、実は多くのプログラミング言語で使用が推奨されていません。

そもそもgoto文が生まれたのは、コンピュータの黎明期に遡ります。

当時は、プログラムの制御構造が現在ほど発達しておらず、goto文は必要不可欠な存在でした。

しかし、時代とともにプログラミング言語は進化を遂げ、より高度で柔軟な制御構文が登場しました。

その結果、goto文の出番は徐々に減っていったのです。

○goto文の概要と問題点

goto文の基本的な構文は、次のようなものです。

goto ラベル;

// 省略

ラベル:
  // 処理

このように、goto文の後にラベルを指定することで、プログラムの実行位置をそのラベルの位置に移動させることができます。

一見便利そうですが、goto文の使用には大きな問題点があります。

goto文を多用すると、プログラムの流れが複雑になり、可読性が大きく損なわれてしまいます。

“スパゲッティコード”と呼ばれる、絡まり合ったようなコードを生み出す原因にもなります。

さらに、goto文を使ったプログラムは、バグを引き起こしやすく、メンテナンスも難しくなります。

○JavaScriptにおけるgoto文の扱い

さて、本記事のテーマであるJavaScriptにおいては、goto文がどのように扱われているのでしょうか。

結論から言うと、JavaScriptにはgoto文が存在しません。

もちろん、JavaScriptにもプログラムの実行位置を移動させる手段は用意されています。

しかし、それはgoto文ではなく、より洗練された制御構文や関数、ラベル付きステートメントなどを使って実現します。

JavaScriptでgoto文と同等の処理を行う方法は、一つではありません。

状況に応じて適切な方法を選ぶことが大切です。

次章では、そんなJavaScriptにおけるgoto文の代替方法を、サンプルコードを交えて詳しく解説していきます。

初心者の方にもわかりやすく、同時に上級者の方にも参考になる内容をお届けできればと思います。

goto文は過去の遺物だと思われがちですが、その代替方法を学ぶことは、JavaScriptのプログラミングスキルを向上させる上で非常に重要です。

では、{{start}}から{{finish}}までの目次に沿って、JavaScriptでgoto文の代替となる方法を解説していきます。

●JavaScriptでgoto文の代替となる14の方法

前章で、JavaScriptにはgoto文が存在しないことを説明しましたが、それではJavaScriptでgoto文と同等の処理を行うにはどうすればよいのでしょうか。

ここからは、そんなgoto文の代替方法を14個のサンプルコードとともに紹介していきます。

JavaScriptには、様々な制御構文や関数、ラベル付きステートメントなどが用意されています。

これらを上手に活用することで、goto文を使わずにプログラムの流れを自在に制御できるようになります。

初心者の方も、サンプルコードを手がかりに一緒に学んでいきましょう。

○サンプルコード1:if文とbreak文を使った分岐

まずは、if文とbreak文を組み合わせた分岐処理の例です。

let i = 0;
while (true) {
  console.log(i);
  i++;
  if (i === 5) {
    break;
  }
}

実行結果

0
1
2
3
4

このコードでは、while文の中でカウンター変数iを1ずつ増やしながら、iが5になったらbreak文でループを抜けています。

goto文を使わずに、特定の条件でループを中断することができます。

if文の条件式を工夫することで、より複雑な分岐処理も実現できます。

例えば、複数の条件を組み合わせたり、論理演算子を使ったりすることで、プログラムの流れを自在に制御できるようになります。

○サンプルコード2:switch文とbreak文を使った分岐

switch文とbreak文を使うことで、より読みやすい分岐処理を書くことができます。

let fruit = "apple";
switch (fruit) {
  case "apple":
    console.log("This is an apple.");
    break;
  case "banana":
    console.log("This is a banana.");
    break;
  default:
    console.log("Unknown fruit.");
    break;
}

実行結果

This is an apple.

このコードでは、fruitの値に応じて異なるメッセージを出力しています。

switch文の各caseでbreak文を使うことで、条件に合致した処理を実行した後、switch文を抜けることができます。

switch文は、if-else if-else文の代替としてよく使われます。

多岐にわたる条件分岐を、より簡潔に書くことができるでしょう。

また、default句を使えば、どの条件にも合致しない場合の処理も簡単に記述できます。

○サンプルコード3:continueとラベル付きループの組み合わせ

continueとラベル付きループを組み合わせることで、ループ処理をより柔軟に制御できます。

outer:
for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) {
      continue outer;
    }
    console.log(i, j);
  }
}

実行結果

0 0
0 1
0 2
1 0
2 0
2 1
2 2

このコードでは、二重のforループの中で、i=1かつj=1の場合にcontinue文が実行されます。

このとき、continueに指定されたラベル(outer)の位置までループ処理がスキップされるため、(1, 1)の組み合わせだけが出力されません。

ラベル付きループとcontinue文を使えば、入れ子になったループ処理も自在に操ることができます。

特定の条件を満たした場合に、内側のループだけでなく外側のループまでスキップしたいときなどに便利です。

ただし、ラベル付きループはコードの可読性を下げる可能性もあるので、乱用は避けるべきです。

より分かりやすい方法がないか、常に検討するようにしましょう。

○サンプルコード4:try-catch-finallyを使った例外処理

ここからは、もう少し応用的なテクニックにチャレンジしていきましょう。

例外処理や非同期処理など、JavaScriptのより高度な機能を活用することで、goto文の代替方法の幅が広がります。

コードが少し複雑になるかもしれませんが、一緒に頑張って理解していきましょう。

プログラムの実行中にエラーが発生した場合、try-catch-finally文を使って例外処理を行うことができます。

これを応用することで、goto文と同様の処理を実現できます。

function processData(data) {
  try {
    if (data === null) {
      throw new Error("Data is null");
    }
    console.log("Processing data:", data);
  } catch (error) {
    console.error("Error:", error.message);
  } finally {
    console.log("Cleanup");
  }
}

processData(null);
processData("Hello, World!");

実行結果

Error: Data is null
Cleanup
Processing data: Hello, World!
Cleanup

このコードでは、processData関数の中でデータの検証を行っています。dataがnullの場合、throw文で意図的にErrorをスローしています。この例外はcatch節で捕捉され、エラーメッセージが出力されます。

finally節は、例外の発生有無に関わらず必ず実行されるため、クリーンアップ処理などを記述するのに適しています。

try-catch-finally文を使えば、エラー発生時の処理を集約できるため、コードの可読性が向上します。

また、例外が発生した場合でも、finally節でリソースの解放などを確実に行えるのが大きなメリットです。

○サンプルコード5:再帰関数を使った繰り返し処理

再帰関数を使うことで、ループ処理を表現することができます。

これを応用すれば、goto文と同等の処理を実現できます。

function countDown(n) {
  console.log(n);
  if (n > 0) {
    countDown(n - 1);
  }
}

countDown(5);

実行結果

5
4
3
2
1
0

このコードでは、countDown関数が自分自身を呼び出すことで、再帰的にカウントダウンを行っています。

nが0より大きい場合、countDown(n – 1)で自分自身を呼び出し、nの値を1つずつ減らしていきます。

nが0以下になると、再帰呼び出しが止まり、関数が終了します。

再帰関数を使えば、ループ処理を直感的に表現できます。

また、適切な終了条件を設定することで、無限ループを防ぐことができます。

ただし、再帰呼び出しが深くなりすぎると、スタックオーバーフローを引き起こす可能性があるので注意が必要です。

○サンプルコード6:setTimeoutを使った遅延実行

setTimeoutを使うことで、指定した時間が経過した後に処理を実行することができます。

これを応用すれば、goto文のような処理を実現できます。

console.log("Start");

setTimeout(() => {
  console.log("Delayed execution");
}, 1000);

console.log("End");

実行結果

Start
End
Delayed execution

このコードでは、setTimeoutを使って、1秒(1000ミリ秒)後に”Delayed execution”というメッセージを出力するようにしています。

setTimeoutに渡されたコールバック関数は、指定された時間が経過した後に非同期で実行されます。

そのため、”End”が先に出力され、その後に”Delayed execution”が出力されます。

setTimeoutを使えば、処理の実行タイミングを制御できます。

これは、一定時間後に処理を再開したい場合や、重い処理を別のタイミングで実行したい場合などに便利です。

ただし、setTimeoutはあくまで非同期処理なので、実行順序には注意が必要です。

○サンプルコード7:クリックイベントリスナーの例

イベントリスナーを使うことで、ユーザーの操作に応じて処理を実行することができます。

これを応用すれば、goto文のような処理を実現できます。

<button id="myButton">Click me</button>
const button = document.getElementById("myButton");

button.addEventListener("click", () => {
  console.log("Button clicked!");
});

実行結果

(ボタンをクリックすると)
Button clicked!

このコードでは、HTMLのbuttonタグにイベントリスナーを設定しています。

ボタンがクリックされると、登録されたコールバック関数が実行され、”Button clicked!”というメッセージが出力されます。

イベントリスナーを使えば、ユーザーの操作に応じてプログラムの流れを制御できます。

これは、インタラクティブなWebアプリケーションを作る上で欠かせない機能です。

また、イベントリスナーを使うことで、処理の実行タイミングをユーザーに委ねることができるため、柔軟性が高まります。

○サンプルコード8:Promiseとasync/awaitを使った非同期処理

JavaScriptにおける非同期処理の代表格といえば、PromiseとAsync/Awaitです。

これを使うことで、非同期処理を同期的に書くことができます。

function fetchData(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      } else {
        reject(new Error(xhr.statusText));
      }
    };
    xhr.onerror = () => {
      reject(new Error("Network error"));
    };
    xhr.send();
  });
}

async function main() {
  try {
    const data = await fetchData("https://api.example.com/data");
    console.log(data);
  } catch (error) {
    console.error("Error:", error);
  }
}

main();

実行結果(成功時)

{"message": "Hello, World!"}

実行結果(失敗時)

Error: Not Found

このコードでは、fetchData関数内でPromiseを使って非同期処理を行っています。

Promiseのコンストラクタに渡されたコールバック関数内で、XMLHttpRequestを使ってデータをフェッチしています。

データの取得に成功すればresolve関数で結果を返し、失敗すればreject関数でエラーを返します。

main関数では、async/awaitを使ってfetchData関数を呼び出しています。

awaitキーワードを使うことで、Promiseの結果が返されるまで処理を待つことができます。

try-catch文を使えば、エラーハンドリングも簡潔に書けます。

Promiseとasync/awaitを使えば、非同期処理を同期的に書くことができるため、コードの読みやすさが大幅に向上します。

また、エラーハンドリングも統一的に行えるため、堅牢なコードを書くことができます。

○サンプルコード9:ジェネレーター関数の例

ジェネレーター関数を使うと、関数の実行を途中で停止し、再開することができます。

これを応用すれば、非同期処理を同期的に書くことができます。

function* generator() {
  console.log("Start");
  yield 1;
  console.log("Middle");
  yield 2;
  console.log("End");
}

const gen = generator();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);

実行結果

Start
1
Middle
2
End
undefined

このコードでは、ジェネレーター関数generatorを定義しています。

ジェネレーター関数内では、yieldキーワードを使って値を返しつつ、関数の実行を一時停止しています。

ジェネレーター関数を呼び出すと、ジェネレーターオブジェクトが返されます。

このオブジェクトのnextメソッドを呼び出すことで、ジェネレーター関数の実行を再開することができます。

nextメソッドは、yieldで返された値をvalueプロパティに持つオブジェクトを返します。

ジェネレーター関数を使えば、非同期処理を同期的に書くことができます。

また、関数の実行を細かく制御できるため、柔軟性の高いコードを書くことができます。

○サンプルコード10:配列のmapメソッドを使った処理

配列のmapメソッドを使うことで、配列の要素を別の値に変換することができます。

これを応用すれば、goto文を使わずに配列の処理を行うことができます。

const numbers = [1, 2, 3, 4, 5];

const doubledNumbers = numbers.map((num) => num * 2);
console.log(doubledNumbers);

実行結果

[2, 4, 6, 8, 10]

このコードでは、numbersという配列に対してmapメソッドを適用しています。

mapメソッドに渡したコールバック関数では、各要素を2倍にしています。

その結果、元の配列の要素が2倍された新しい配列が返されます。

mapメソッドを使えば、配列の要素を別の値に変換することができます。

また、コールバック関数内で条件分岐を行えば、より複雑な処理も可能です。

配列の処理を関数型のアプローチで行うことで、コードの可読性と再利用性が向上します。

○サンプルコード12:クロージャーを使った例

クロージャーは、JavaScriptにおける重要な概念の一つです。

外側の関数が終了した後も、内側の関数が外側の関数の変数にアクセスできる仕組みを指します。

function counter() {
  let count = 0;
  return function() {
    count++;
    console.log(count);
  };
}

const increment = counter();
increment();
increment();
increment();

実行結果

1
2
3

このコードでは、counter関数内で定義されたcount変数と、その値を増加させる内部関数を返しています。counter関数を呼び出すと、内部関数が返されます。

この内部関数は、count変数を参照し、その値を増加させます。

重要なのは、counter関数が終了した後も、内部関数がcount変数にアクセスできることです。これがクロージャーの特徴です。

クロージャーを使うことで、プライベートな変数を持つ関数を作ることができます。

クロージャーは、モジュール化や情報の隠蔽、関数型プログラミングなどに活用されます。

JavaScriptのスコープとクロージャーの仕組みを理解することは、より高度なプログラミングをする上で欠かせません。

○サンプルコード13:IIFEを使ったスコープの例

IIFE(Immediately Invoked Function Expression)は、関数を定義すると同時に実行するテクニックです。

これを使うことで、関数内で定義した変数のスコープを制限することができます。

(function() {
  const message = "Hello, IIFE!";
  console.log(message);
})();

console.log(message);

実行結果

Hello, IIFE!
ReferenceError: message is not defined

このコードでは、IIFEの中で定義されたmessage変数は、IIFE内でのみ有効です。

IIFE外部からmessage変数にアクセスしようとすると、ReferenceErrorが発生します。

IIFEを使うことで、グローバルスコープの汚染を防ぐことができます。

また、プライベートな変数や関数を定義することもできます。

IIFEは、JavaScriptのモジュールパターンの基礎となる概念です。

○サンプルコード14:モジュールパターンの実装例

モジュールパターンは、クロージャーとIIFEを組み合わせた、JavaScriptにおける代表的なデザインパターンです。

モジュールパターンを使うことで、プライベートな変数や関数を持つオブジェクトを作ることができます。

const myModule = (function() {
  let privateVariable = "I am private";

  function privateMethod() {
    console.log("This is a private method");
  }

  return {
    publicMethod: function() {
      console.log("This is a public method");
      privateMethod();
    },
    publicVariable: "I am public"
  };
})();

myModule.publicMethod();
console.log(myModule.publicVariable);
console.log(myModule.privateVariable);

実行結果

This is a public method
This is a private method
I am public
undefined

このコードでは、IIFEの中で定義されたprivateVariableとprivateMethodは、モジュールの外部からアクセスできません。

一方、publicMethodとpublicVariableは、返されたオブジェクトのプロパティとして公開されています。

モジュールパターンを使うことで、関連する変数や関数をグループ化し、外部との干渉を防ぐことができます。

また、公開APIを通じて、モジュールの機能を制御することができます。

モジュールパターンは、大規模なアプリケーション開発において重要な役割を果たします。

●よくあるエラーと対処法

これまで、JavaScriptにおけるgoto文の代替方法を様々な角度から探ってきました。

if文やswitch文、try-catch-finally文、再帰関数、イベントリスナー、Promiseとasync/await、ジェネレーター関数、配列のmapメソッド、クロージャー、IIFE、モジュールパターンなど、多岐にわたるテクニックを解説しました。

これらの代替方法を使いこなせば、goto文を使わずに複雑な処理の流れを制御できるようになるでしょう。

しかし、初心者の方にとっては、これらのテクニックを使う際に、つまずきやすい点があるかもしれません。

ここからは、goto文の代替方法を使う際によく遭遇するエラーと、その対処法について解説していきます。

エラーメッセージの意味を理解し、適切な修正を加えることができれば、よりスムーズにJavaScriptプログラミングを進められるはずです。

それでは、具体的なエラーとその対処法を見ていきましょう。

○SyntaxError: Illegal continue statement

このエラーは、continue文が使える場所以外で使用された場合に発生します。

continue文は、ループ内でのみ使用できます。

// 誤った例
function fn() {
  continue; // SyntaxError: Illegal continue statement
}

// 正しい例
function fn() {
  for (let i = 0; i < 10; i++) {
    if (i === 5) {
      continue;
    }
    console.log(i);
  }
}

上記の誤った例では、continue文がループ外で使用されているため、エラーが発生します。

正しい例のように、continue文はループ内で使用する必要があります。

このエラーを避けるためには、continue文の使用場所を確認し、ループ内で使用されていることを確認しましょう。

また、ループを使わずに同様の処理を実現する方法を検討するのも一つの選択肢です。

○ReferenceError: goto is not defined

このエラーは、goto文がJavaScriptで使用できないことを表しています。

JavaScriptにはgoto文が存在しないため、goto文を使用しようとするとこのエラーが発生します。

// 誤った例
function fn() {
  goto label; // ReferenceError: goto is not defined
  label:
  console.log("Hello");
}

このエラーを避けるためには、goto文を使用せず、他の制御構文や関数、ラベル付きステートメントなどを使用して、同等の処理を実現する必要があります。

この記事で紹介したgoto文の代替方法を参考に、適切な方法を選択しましょう。

○ラベル付きステートメントの書き方ミス

ラベル付きステートメントを使用する際に、ラベルの書き方を間違えるとエラーが発生します。

// 誤った例
function fn() {
  label: // SyntaxError: Unexpected token ':'
  for (let i = 0; i < 10; i++) {
    // ...
  }
}

// 正しい例
function fn() {
  label:
  for (let i = 0; i < 10; i++) {
    // ...
  }
}

上記の誤った例では、ラベルの後にコロン(:)が続いていますが、その後にステートメントがないため、エラーが発生します。

正しい例のように、ラベルの後には改行し、ステートメントを記述する必要があります。

このエラーを避けるためには、ラベル付きステートメントの書き方を正しく理解し、適切に使用することが重要です。

また、ラベル付きステートメントの乱用は避け、可読性の高いコードを心がけましょう。

●ユースケース

実際のプログラミングでは、状態遷移の制御や例外処理の一元管理、巨大な分岐処理の置き換えなど、様々な場面でgoto文の代替方法が活躍します。

これらのユースケースを理解することで、goto文の代替方法の実践的な使い方がイメージできるようになるでしょう。

それでは、具体的なユースケースを一つずつ見ていきましょう。

○状態遷移の制御

アプリケーションの開発では、状態遷移の制御が重要な役割を果たします。

例えば、ゲームアプリケーションでは、プレイヤーの行動に応じて、ゲームの状態が遷移していきます。

この状態遷移を制御する際に、goto文の代替方法が活用できます。

具体的には、ステートマシンパターンを使って状態遷移を表現することができます。

ステートマシンパターンでは、各状態を表すオブジェクトを定義し、状態間の遷移をメソッドとして実装します。

この際、if文やswitch文、オブジェクトのメソッドなどを使って、状態遷移の制御を行います。

const states = {
  IDLE: {
    start() {
      console.log("ゲームを開始します");
      return states.PLAYING;
    }
  },
  PLAYING: {
    pause() {
      console.log("ゲームを一時停止します");
      return states.PAUSED;
    },
    end() {
      console.log("ゲームを終了します");
      return states.IDLE;
    }
  },
  PAUSED: {
    resume() {
      console.log("ゲームを再開します");
      return states.PLAYING;
    },
    end() {
      console.log("ゲームを終了します");
      return states.IDLE;
    }
  }
};

let currentState = states.IDLE;
currentState = currentState.start();
currentState = currentState.pause();
currentState = currentState.resume();
currentState = currentState.end();

実行結果

ゲームを開始します
ゲームを一時停止します
ゲームを再開します
ゲームを終了します

このように、ステートマシンパターンを使うことで、状態遷移を明確に表現できます。

各状態をオブジェクトとして定義し、状態間の遷移をメソッドとして実装することで、状態遷移の制御を直感的に行うことができます。

○例外処理の一元管理

アプリケーションの開発では、例外処理も重要な役割を果たします。

例外が発生した際に、適切に処理を行わないと、アプリケーションが予期せず終了してしまったり、ユーザーに不便を与えたりする可能性があります。

goto文の代替方法の一つであるtry-catch-finally文を使うことで、例外処理を一元的に管理することができます。

try-catch-finally文を使えば、例外が発生した際の処理を集約でき、コードの可読性が向上します。

function fetchData(url) {
  try {
    // データを取得する処理
    // ...
    if (/* エラーが発生した場合 */) {
      throw new Error("データの取得に失敗しました");
    }
    // ...
  } catch (error) {
    console.error("エラーが発生しました:", error);
    // エラー処理
    // ...
  } finally {
    console.log("処理を終了します");
    // リソースの解放など
    // ...
  }
}

fetchData("https://api.example.com/data");

実行結果(エラーが発生した場合)

エラーが発生しました: Error: データの取得に失敗しました
処理を終了します

このように、try-catch-finally文を使うことで、例外処理を一箇所にまとめることができます。

例外が発生した場合は、catch節で適切なエラー処理を行い、finally節でリソースの解放などの後処理を行います。

例外処理を一元管理することで、コードの可読性が向上し、メンテナンス性も向上します。

また、例外処理を集約することで、例外が発生した際の動作を統一的に制御できます。

○巨大な分岐処理の置き換え

アプリケーションの開発では、複雑な分岐処理を扱う必要があることがあります。

if文やswitch文を使った分岐処理が大きくなってしまうと、コードの可読性が低下し、メンテナンス性も悪化します。

goto文の代替方法の一つである、オブジェクトのメソッドを使うことで、巨大な分岐処理を置き換えることができます。

分岐条件をオブジェクトのプロパティとして定義し、分岐処理をメソッドとして実装することで、分岐処理を分かりやすく表現できます。

const actions = {
  create() {
    console.log("データを作成します");
    // ...
  },
  read() {
    console.log("データを読み取ります");
    // ...
  },
  update() {
    console.log("データを更新します");
    // ...
  },
  delete() {
    console.log("データを削除します");
    // ...
  }
};

function performAction(action) {
  const fn = actions[action];
  if (fn) {
    fn();
  } else {
    console.log("不正なアクションです");
  }
}

performAction("create");
performAction("read");
performAction("update");
performAction("delete");
performAction("invalid");

実行結果

データを作成します
データを読み取ります
データを更新します
データを削除します
不正なアクションです

このように、オブジェクトのメソッドを使うことで、分岐処理を分かりやすく表現できます。

分岐条件をオブジェクトのプロパティとして定義し、分岐処理をメソッドとして実装することで、コードの可読性が向上します。

また、この手法を使えば、分岐条件の追加や削除が容易になります。

新しい分岐条件を追加する場合は、オブジェクトに新しいプロパティとメソッドを追加するだけで済みます。

巨大な分岐処理を置き換えることで、コードの可読性とメンテナンス性が向上し、バグの発生を防ぐことができます。

また、分岐処理をオブジェクトとして表現することで、コードの再利用性も高まります。

まとめ

開発者の皆さん、お疲れ様でした。

この記事では、goto文について理解を深め、その代替方法を14個のサンプルコードとともに徹底的に解説してきました

この記事で学んだことを活かし、より読みやすく、メンテナンスしやすいJavaScriptコードを書いていきましょう。

goto文の代替方法を適切に使いこなすことで、コードの品質と生産性を向上させることができるはずです。