ITスキル・ノウハウ

JavaScriptのスコープ徹底攻略!グローバル、関数、ブロックの使い分けと落とし穴

2024年10月6日

JavaScriptのスコープ画像

こんにちは、フリーランスエンジニアになって1年のぽんねぐです!

JavaScriptを使ってコードを書いていると、「ReferenceError: '変数名' is not defined」というエラーメッセージに遭遇したことはありませんか?例えば、以下のコードをHTMLファイルとして作成し、ブラウザで開いて見てください。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JavaScriptのスコープエラー例</title>
</head>
<body>
    <h1>JavaScriptのスコープによるエラー例</h1>
    <p>ブラウザのコンソールでエラーメッセージを確認してください。</p>

    <script>
        function exampleFunction() {
            if (true) {
                let blockVar = "I'm inside the block!";
            }
            console.log(blockVar); // ここでエラー発生!
        }
        exampleFunction();
    </script> 

    <p>コンソールを開く方法は、右クリックして「検証」を選択し、「Console」タブをクリックしてください。</p>
</body>
</html>

【実行結果】

is not definedエラー画像

一見、問題なく動きそうなこのコード。しかし実行すると、blockVarが定義されていないというエラーが出ます。この原因は「スコープ」にあります。スコープを正しく理解しないと、思いもよらないバグが発生し、デバッグに時間を取られることが多々あります。

この記事では、JavaScriptにおける変数スコープの基本から実践的な使い方まで、具体例を交えながら詳しく解説していきます!

また、以下に合わせて読みたい記事を紹介します。

はじめに:スコープとは何?その重要性を解説

JavaScriptにおける「スコープ」とは、変数がどこから参照できるか(アクセスできるか)を決定する範囲を指します。プログラムを書く際、変数や関数は特定のスコープ内でのみ有効です。スコープの理解は、バグを減らし、コードを効率よく書くために非常に重要です。

例えば、異なる部分で同じ変数名を使いたいとき、スコープを使って意図しない変数の衝突を防ぎます。正しいスコープの使い方を理解すれば、変数の寿命を適切に管理し、メモリ消費の無駄も減らせます。

スコープの3種類を完全解説!

スコープには主に3種類あります。それぞれの役割や使い方を詳しく見ていきましょう。

グローバルスコープ:全ての場所から見える変数のリスクとは?

グローバルスコープとは、プログラム全体でアクセス可能なスコープのことです。グローバル変数は、どこからでも参照できるため便利ですが、全ての場所から変更できてしまうため、不具合が発生しやすいというリスクがあります。

var globalVar = "I am a global variable!"; // グローバル変数

function showGlobalVar() {
  console.log(globalVar); // どこからでもアクセス可能
}

showGlobalVar(); // "I am a global variable!" が出力される

グローバルスコープは便利ですが、使いすぎると予期しない動作や、他の開発者との協力時に変数の衝突が起こりやすくなります。そのため、できるだけ局所的なスコープを利用するのが望ましいです。

関数スコープ:関数内の安全な変数管理の秘訣

関数スコープとは、関数内で定義された変数が、関数の外からは参照できないスコープのことです。関数スコープ内の変数は、関数の実行が終わると自動的に削除されます。

function myFunction() {
  var localVar = "I am a local variable"; // 関数スコープ内の変数
  console.log(localVar); // "I am a local variable" が出力される
}

myFunction();
console.log(localVar); // エラー:localVarは定義されていない

上記の例では、localVarは関数の外からはアクセスできません。このように、関数内で使用する変数は、関数の外で干渉されることがないため安全です。

ブロックスコープ:letconstでコードを最適化!

JavaScript ES6以降、letconstを使ってブロックスコープを作成できるようになりました。ブロックスコープとは、{}で囲まれた範囲内でのみ有効なスコープです。varは関数スコープに従うため、ブロック内で宣言しても外部からアクセスできるのに対し、letconstはブロックスコープを遵守します。

if (true) {
  let blockVar = "I am a block-scoped variable";
  console.log(blockVar); // 出力される
}

console.log(blockVar); // エラー:blockVarは定義されていない

ブロックスコープは特に、ループ内や条件分岐内での変数の取り扱いに有用です。letconstを使うことで、ブロック外に変数が漏れ出さないように管理できます。

スコープチェーンとは?内側から外側へ辿る変数の道

JavaScriptでは、関数内で外部のスコープにある変数を参照することができます。これをスコープチェーンと呼びます。スコープチェーンは、関数がネストされるたびに、内側の関数が外側の変数にアクセスする仕組みです。

var outerVar = "I'm outside!";

function outerFunction() {
  var innerVar = "I'm inside!";
  
  function innerFunction() {
    console.log(outerVar); // "I'm outside!" が出力される
  }
  
  innerFunction();
}

outerFunction();

上記の例では、innerFunctionouterVarにアクセスできます。スコープチェーンを理解すると、複雑な関数のネストや変数の管理が簡単になります。

var vs let vs const:それぞれのスコープと使い方を徹底比較

varの特性と巻き上げの問題点

varは、関数スコープを持ちますが、巻き上げ(ホイスティング)という特性があり、宣言された位置に関わらず、スコープの最初で定義されたかのように扱われます。

console.log(hoistedVar); // undefined
var hoistedVar = "I am hoisted!";

このように、varは変数が宣言される前でも参照可能ですが、値はまだ割り当てられていないためundefinedになります。これが原因で、予期しないバグが発生することがあります。

letconstで安全なスコープ管理

letconstは、ブロックスコープを持つため、スコープの範囲を明確に管理できます。特に、constは再代入不可なので、一定の値が変わらないことを保証したいときに使います。

let a = 10;
a = 20; // OK

const b = 30;
b = 40; // エラー:再代入不可

クロージャーとスコープの関係:パワフルなクロージャーの正体

クロージャーは、関数内で定義された関数が、外部のスコープにある変数にアクセスできる機能です。クロージャーを利用すると、関数内のデータを保持したり、特定の条件下で実行される関数を作成することが可能です。

function outer() {
  var outerVar = "I'm outer!";
  
  return function inner() {
    console.log(outerVar); // "I'm outer!" が出力される
  };
}

var closure = outer();
closure();

このように、inner関数はouterVarにアクセスし続けます。クロージャーは特に、プライベート変数の実装やコールバック関数で活用されます。

スコープとメモリ管理:不要なメモリを抱え込まないコツ

スコープ内で使われた変数は、そのスコープを超えると自動的にメモリから解放されます。しかし、クロージャーを使った場合など、スコープ外でも変数が保持されることがあり、これがメモリリークにつながる場合があります。

function createCounter() {
  let count = 0;
  
  return function() {
    return ++count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

この例では、count変数はcreateCounterが終了してもメモリに保持され、counter関数を実行するたびに値が増加します。

まとめ

今回の記事では、JavaScriptのスコープについて具体例を挙げながら説明しました。

スコープの理解は、コードのエラーハンドリングや保守性の向上に役立ちます。グローバルスコープは便利ですが、乱用すると予期せぬバグを引き起こす可能性があります。letconstを使って、できるだけ局所的なスコープで変数を管理し、クロージャーの仕組みを活用して柔軟なコードを書けるようにしましょう。

エンジニアの相談画像

何か不明点などございましたらコメント欄に記載頂けたらと思います!

以上、最後までお読みいただきありがとうございました。

-ITスキル・ノウハウ