グローバル変数とローカル変数のスコープ

この記事では、スコープと、JavaScript でのスコープの仕組みについて説明します。

スコープは、変数がアクセスおよび使用されるコンテキストを定義する、JavaScript やその他のプログラミング言語の基本的な概念です。JavaScript について学び、変数の操作を続けるうちに、コードがより有用になり、適用範囲が広がります。

スコープは以下に役立ちます。

  • メモリをより効率的に使用する: スコープにより、必要な場合にのみ変数を読み込むことができます。変数がスコープ外の場合、現在実行中のコードで使用できるようにする必要はありません。
  • バグの検出と修正が容易になる: 変数をローカル スコープで分離すると、コード内のバグのトラブルシューティングが容易になります。グローバル変数とは異なり、外部スコープからのコードはローカル スコープの変数を操作できないと信頼できるからです。
  • 再利用可能なコードの小さなブロックを作成する: たとえば、外部スコープに依存しない純粋な関数を作成できます。このような関数は、最小限の変更で簡単に別の場所に移動できます。

対象範囲とは

変数のスコープにより、コード内のどこで変数を使用できるかが決まります。

JavaScript はグローバル スコープまたはローカル スコープの変数を定義します。

  • グローバル スコープを持つ変数は、JavaScript コード内の他のすべてのスコープから使用できます。
  • ローカル スコープを持つ変数は、特定のローカル コンテキスト内でのみ使用できます。また、varletconst などのキーワードによって作成されます。varlet、または const キーワードを使用して関数内で変数を作成する場合、その変数にはローカル スコープが設定されます。

この記事の後半のセクションでは、ブロックと語彙のスコープについて説明します。

  • ブロック スコープ変数は、ブロック ステートメントが定義されている中かっこの場所によって、ブロックでローカルに利用できます。ブロック スコープを持つのは、let キーワードまたは const キーワードで宣言された変数のみです。
  • 語彙スコープは、ソースコード内で変数が宣言されている場所を使用して、その変数が使用可能な場所を決定します。クロージャを使用して、囲まれた関数が、語彙環境と呼ばれる外側のスコープで参照される変数にアクセスできるようにします。

変数がそのスコープ内でアクセスされると、JavaScript は割り当てられた値を返すか、それ以外の場合はエラーを生成します。

変数を宣言するには:

  • ローカル変数またはグローバル スコープ変数を宣言するには、varconst、または let キーワードを使用します。
  • ブロック スコープ変数を宣言するには、const キーワードまたは let キーワードを使用します。

関数内で var 変数を宣言すると、その宣言により、その変数は最も近い関数で使用可能になります。var キーワードを使用してブロック スコープを持つ変数を宣言することはできません。

スコープの例

この例では、greeting 変数が関数またはブロックの外部で宣言され、その値が現在のドキュメント内のすべてのコードで利用可能になるため、グローバル スコープを示しています。

const greeting = 'hello';
console.log(greeting); // 'hello'

グローバル スコープの例では、greeting 変数に hello 値が割り当てられます。

この例では、関数内で let キーワードを使用して greeting 変数を宣言しているため、ローカル スコープを示しています。greeting 変数はローカル スコープの変数であり、関数外では使用できません。

function greet() {
  let greeting = 'Hello World!';
  console.log(greeting);
}

この例では、ブロック内で greeting 変数を宣言し、中かっこ内でのみ変数にアクセスできるようにブロック スコープを示しています。

if (true) {
   const greeting = 'hello';
}

console.log(greeting); // ReferenceError: greeting is not defined

console.log 関数が greeting 変数の値を出力しようとすると、JavaScript は想定される hello メッセージではなく、ReferenceError エラー メッセージを返します。その理由は、

greeting 変数にブロック スコープがあり、最も近いブロックが if 条件文の一部であるため、エラーが返されます。ブロック内で宣言する let 変数と const 変数に、ブロックの外部からアクセスすることはできません。したがって、ブロック スコープを指定する中かっこ内の greeting 変数にのみアクセスできます。

この例では、console.log(message) メソッドを中かっこ内で移動しているため、エラーを修正しています。更新したコードは、console.log(message) メソッドをブロック内で再配置します。

if (true) {
   const greeting = 'hello';
   console.log(greeting);
}

スコープのタイプ

グローバル スコープ

プログラムのどこからでも、グローバル スコープを持つ変数にアクセスできます。

2 つの JavaScript ファイル(file-1.jsfile-2.js)をインポートする HTML ファイルについて考えてみましょう。

<script src="file-1.js"></script>
<script src="file-2.js"></script>

この例では、globalMessage 変数にグローバル スコープがあり、関数の外部で書き込まれています。実行時および実行中は、JavaScript プログラムのどこからでも globalMessage 変数の値にアクセスできます。

file-1.js ファイルと file-2.js ファイルの内容は、次のコード スニペットで確認できます。どちらのファイルでも globalMessage 変数を使用できることに注意してください。

// file-1.js
function hello() {
    var localMessage = 'Hello!';
}

var globalMessage = 'Hey there!';

// file-2.js
console.log(localMessage); // localMessage is not defined
console.log(globalMessage); // Hey there!

また、この記事では詳しく説明していない別のタイプのスコープもあります。JavaScript モジュール内の関数またはブロックの外で変数を作成した場合、その変数にはグローバル スコープではなくモジュール スコープが設定されます。モジュール スコープを持つ変数は、現在のモジュール内の任意の場所で使用できますが、他のファイルやモジュールからは使用できません。モジュール スコープの変数を他のファイルで使用できるようにするには、作成されたモジュールから変数をエクスポートし、変数にアクセスする必要があるモジュールからimportする必要があります。

ローカル スコープと関数スコープ

varlet、または const キーワードを使用して JavaScript 関数で変数を作成すると、その変数は関数に対してローカルであるため、関数内からのみアクセスできます。ローカル変数は関数の開始時に作成され、関数の実行が終了すると実質的に削除されます。

この例では、addNumbers() 関数内で total 変数を宣言しています。addNumbers() 関数内では、ab,total 変数にのみアクセスできます。

function addNumbers(a, b) {
    const total = a + b;
}

addNumbers(3, 4);

let キーワードと const キーワードを使用して変数に名前を付けることができます。let キーワードを使用すると、JavaScript で変数を更新できます。ただし、const キーワードを使用すると、変数は定数のままになります。

var variable1 = 'Declared with var';
var variable1 = 'Redeclared with var';
variable1; // Redeclared with var

let variable2 = 'Declared with let. Cannot be redeclared.';
variable2 = 'let cannot be redeclared, but can be updated';
variable2; // let cannot be redeclared, but can be updated

const variable3 = 'Declared with const. Cannot be redeclared or updated';
variable3; // Declared with const. Cannot be redeclared or updated

ブロック スコープ

ブロックは、1 つのステートメントまたは一連のステートメントをグループ化するために使用します。ブロック スコープのローカル変数を宣言するには、const キーワードまたは let キーワードを使用します。var キーワードを使用してブロック スコープを持つ変数を宣言することはできません。

たとえば、このブロックでは、name 変数とその "Elizabeth" 値のスコープが中かっこ内に含まれています。ブロック スコープ内の変数は、ブロックの外部では使用できません。

{
    const name = "Elizabeth";
}

ブロック スコープの変数は、ifforwhile ステートメントで使用できます。

このコード スニペットには 2 つの for ループがあります。1 つの for ループで、var キーワードを使用してイニシャライザ変数を宣言します。イニシャライザ変数は、012 という数値をインクリメントします。他の for ループでは、let キーワードを使用してイニシャライザ変数を宣言します。

for (var i = 0; i < 2; i++) {
    // ...
}

console.log(i); // 2

for (let j = 0; j < 2; j++) {
    // ...
}

console.log(j); // The j variable isn't defined.

上記のコード例では、最初の for ループの i 変数が for ループの外部にリークし、var キーワードでブロック スコープを使用していないため、2 値が保持されています。この問題は、let キーワードで宣言された j 変数のスコープが for ループのブロックに設定され、for ループが終了した後も存在しない 2 番目の for ループで修正されます。

別のスコープで変数名を再利用する

スコープを使用すると、同じ変数名を別のスコープの別の場所で再利用する場合でも、関数内の変数を分離できます。

次の例は、スコープを使用することで、同じ変数名を異なる関数で再利用する方法を示しています。

function listOne() {
    let listItems = 10;
    console.log(listItems); // 10
}

function listTwo() {
   let listItems = 20;
   console.log(listItems); // 20
}

listOne();
listTwo();

listOne() 関数と listTwo() 関数の listItems 変数には想定値が割り当てられているため、互いに競合しないでください。

クロージャと語彙のスコープ

クロージャとは、内側の関数が外側の関数スコープにアクセスできる密閉された関数のことです。このスコープは語彙環境とも呼ばれます。そのため、JavaScript ではクロージャを使用して、関数が外部の語彙環境を参照できるようにします。これにより、関数内のコードが、関数の外部で宣言された変数を参照できるようになります。実際、外部の語彙環境への参照チェーンをコーディングして、ある関数が関数から呼び出され、その関数が別の関数から呼び出されるようにすることもできます。

この例では、outer() 関数が呼び出されたときに作成される字句環境でクロージャを形成し、hello 変数をクローズしています。したがって、hello 変数は setTimeout コールバック関数内で使用されます。

function outer() {
    const hello = 'world';

    setTimeout(function () {
        console.log('Within the closure!', hello)
    }, 100);
}

outer();

語彙スコープを使用すると、スコープは実行時ではなくソースコードのコンパイル時に決定されます。語彙環境の詳細については、語彙のスコープとクロージャをご覧ください。

モジュール

JavaScript モジュールは、JavaScript コードの整理に役立ちます。これらを適切に使用することで、コードベースが効果的に構造化され、コードの再利用が容易になります。JavaScript モジュールには、異なるファイル間で変数を共有するためにグローバル変数を使用するのではなく、変数のエクスポートimportを行う手法が用意されています。

// hello.js file
function hello() {
  return 'Hello world!';
}

export { hello };

// app.js file
import { hello } from './hello.js';

console.log(hello()); // Hello world!

スコープ ビジュアライザのデモ

スコープは、すべての JavaScript デベロッパーが理解しておくべき基本概念です。スコープ システムについて理解を深めるには、JS Scope Visualizer で独自のコードを記述してみてください。このデモでは、JavaScript スコープを視覚化できるようにコードの色を使用しています。

まとめ

この記事では、さまざまな種類のスコープについて説明します。JavaScript スコープは、ウェブ開発におけるより高度なコンセプトの一つです。ここまでに目を通し、このトピックを理解できたと思います。

スコープはユーザー向けの機能ではありません。影響を受けるのはコードを作成するウェブ デベロッパーだけですが、スコープの仕組みに関する知識があると、バグが発生したときに修正できます。