JavaScript
 Computer >> コンピューター >  >> プログラミング >> JavaScript

JavaScriptのクロージャとは?基本概念と実用的な活用方法を徹底解説

クロージャ(Closure)とは、関数と、その関数が宣言された際のレキシカル環境(語彙的スコープ)を組み合わせた概念です。内部関数は、自分が定義された外側の関数の変数にアクセスでき、外側の関数の実行が終了した後でもその変数を参照し続けることができます。

クロージャの基本的な仕組み

まず、シンプルな例を見てみましょう。

function outerFunc() {
    var name = "TutorialsPoint"; // outerFunc内で作成されたローカル変数
    return function innerFunc() {
        // innerFunc()は内部関数であり、クロージャである
        console.log(name); // 親関数で宣言された変数を使用
    };
}
let f = outerFunc();
f();

このコードでは、outerFunc() がローカル変数 name と、innerFunc() という関数を作成しています。innerFunc() は outerFunc() の内部で定義された内部関数であり、outerFunc() の関数本体の中でしか利用できません。

注目すべきは、innerFunc() 自身にはローカル変数が一切ないという点です。しかし、内部関数は外側の関数の変数にアクセスできるため、innerFunc() は親関数である outerFunc() 内で宣言された変数 name を参照できます。

通常、outerFunc() の実行が完了すると、name 変数はスコープ外となり参照できなくなるはずです。ところが実際には、返されて f に代入された innerFunc は依然として name 変数へアクセスできます。これこそがクロージャであり、内部関数が外側の関数のレキシカルスコープを「閉じ込めて」保持している状態といえます。

クロージャの実用的な活用例

1. プライベートメソッドのエミュレート

Javaなどの言語では、メソッドを private として宣言でき、同じクラス内の他のメソッドからのみ呼び出せるように制限できます。JavaScriptにはネイティブな仕組みとしてこの機能はありませんが、クロージャを使えばプライベートメソッドを疑似的に実現できます。

サンプルコード

var counter = (() => {
    var privateCounter = 0;
    let changeBy = (val) => privateCounter += val;
    return {
        increment: () => changeBy(1),
        decrement: () => changeBy(-1),
        value: () => privateCounter
    };
})();
console.log(counter.value());
counter.increment();
counter.increment();
console.log(counter.value());
counter.decrement();
console.log(counter.value());

出力結果

0
2
1

この例では、privateCounter と changeBy 関数がクロージャによって外部から直接アクセスできない状態に保たれています。外部から操作できるのは、返されたオブジェクトが公開している increment、decrement、value の3つのメソッドだけです。これにより、状態を安全にカプセル化できます。

2. イベントハンドラでの活用

クロージャはイベントハンドラを扱う場面でも非常に役立ちます。まず、うまく動作しない典型的な例を見てみましょう。

for(let i = 0; i < 4; i++) {
    button = buttons[i]
    button.addEventListener("click", alert(i))
}

buttons コレクションに4つのボタンがあり、上記のようにイベントリスナーを追加しようとした場合、ボタンをクリックしても undefined が表示されてしまいます。これは、クリック時にハンドラが呼び出される時点では、変数 i がすでに定義されていないためです。

この問題を回避するには、変数 i をクロージャで包み込みます。アロー関数で囲むことで、各ループ時点の i の値がキャプチャされ、クリック時に正しい値が表示されるようになります。

for(let i = 0; i < 4; i++) {
    button = buttons[i]
    button.addEventListener("click", () => alert(i))
}

このようにクロージャを活用することで、変数のライフサイクルを意図的に延長したり、データを隠蔽したりと、JavaScriptにおいて柔軟かつ強力なプログラミングが可能になります。

  1. JavaScriptの関数プロトタイプとは?仕組みと使い方を解説

    JavaScriptで作成されたすべての関数には、JavaScriptエンジンによって自動的にprototypeプロパティが追加されます。このprototypeプロパティはオブジェクトであり、デフォルトではconstructorプロパティを含んでいます。関数のプロトタイプには、以下のようにしてアクセスできます。functionName.prototypeコンストラクタ関数を使ってオブジェクトを生成する場合、このprototypeプロパティを活用することで、同じコンストラクタから作成された複数のオブジェクト間でメソッドやプロパティを効率的に共有することができます。これにより、各インスタンスごとに

  2. JavaScriptでのメソッド借用:call()・apply()・bind()の使い方を解説

    JavaScriptでは、call()、apply()、bind()という3つのメソッドを使うことで、あるオブジェクトが持つメソッドを別のオブジェクトが「借用」できます。これは、本来そのメソッドを持たないオブジェクトに対して、thisの参照先を差し替えてメソッドを実行できる便利なテクニックです。 3つのメソッドの違い call():引数をカンマ区切りで個別に渡し、即座に関数を実行します。 apply():引数を配列(または配列風オブジェクト)として渡し、即座に関数を実行します。 bind():thisを固定した新しい関数を返すだけで、即座には実行されません。返された関数は後から好きなタイミン