Help us understand the problem. What is going on with this article?

【JavaScript】クロージャを使ってみよう。

こんにちは、今回はクロージャについて大体理解できた!ということで、
クロージャの使い方をメモしていきたいと思います。

これからクロージャを勉強する方の助けに少しでもなれば幸いです。

クロージャとは

以下のように定義されています。

クロージャは、関数とその関数が宣言されたレキシカル環境の組み合わせ
MDN | クロージャ

どういうこと?という感じですが、簡単に言い換えるなら
「関数とその関数から参照できるローカル変数を使用している状態」と言えると思います。

言葉ではイメージしづらいので、定義については一旦置いておきまして、クロージャを使うとできることを見ていきましょう。

クロージャを使うことで以下のような機能を持った関数を作ることができます。

①プライベート変数を保持する関数
②動的な関数を生成する関数

順番に見ていきましょう。

①プライベート変数を保持する関数

例としてカウンター機能を実装しながら、クロージャの使い方をみていきます。

let counter = 0; //参照できる外部変数

function countUp(){ //1加算する関数
    counter++;
    console.log(counter); // 1
}

countUp(); //  実行

カウンターを実装する為に
まずcountUpという1加算する関数と、関数内で使用するcounter変数を定義します。

この時点でカウンターは完成していますが、このコードには1点問題があります。
それが「counter変数がどこからでも変更できてしまう」という点です。

これを解決するために、クロージャの仕組みを利用します。

先ほどのコードを、以下のように変更します。

function countFactory(){ // 追記①:関数と変数を入れ子にする
    let counter = 0;

    function countUp(){
        counter++;
        console.log(counter); 
    }
    return countUp; // 追記②:countUp関数を返す。
}

突然ですが、JavaScriptでは、上記のように関数の中に関数を宣言すること、そして関数自体を返すことが可能です

よって今回は、新しく定義したcountFactory関数に、先ほどの関数と変数を丸ごと入れます。

そして、ただ入れ子にしただけでは外部からcountUp関数を使用できなくなってしまうため、countUp関数を返してあげましょう。

これでクロージャを利用したカウンターの完成です。

counter変数は、先ほどはグローバルコンテキスト上で宣言されていた為、どこからでもアクセス可能となっていましたが、今回は関数内に宣言されている為、関数実行時にのみアクセス可能になった(プライベートな変数になった)ことが分かります。

では、続いて実行をしてみます。

function countFactory(){ 
    let counter = 0; // プライベートなローカル変数

    function countUp(){
        counter++;
        console.log(counter); 
    }
    return countUp; // 関数を返す。
}

//以下、追記部分

const myCountUp = countFactory(); // まずはcountFactory関数を実行。

myCountUp(); // 1
myCountUp(); // 2
myCountUp(); // 3

まず親関数であるcountFactoryを実行します。
関数が返るので、新しく変数myCountUpという変数を用意し、代入します。

myCountUpは関数になりますので、()を付けることで実行できます。
結果として、実行した分だけ、1.2.3...とカウントアップされていきます。

ポイントとなるのは、counterローカル変数がmyCountUp関数が実行された際にのみ加算されるようになったという点です。

そして本来、関数内のローカル変数は処理が終了すれば破棄されますが、
内側の関数でcounterローカル変数を参照している(つまりクロージャが作られている)為、内側の関数内でcounter変数の参照を維持することができています。

また以下のコードを見てみましょう。

function countFactory(){ 
    let counter = 0; 

    function countUp(){
        counter++;
        console.log(counter); 
    }
    return countUp; 
}

//親関数を2回実行
const myCountUpA = countFactory();
const myCountUpB = countFactory();

親関数を2回実行し、AとBの2つの変数にcountUp関数を返します。

AとB、それぞれ何度か実行して結果を見てみましょう。

myCountUpA(); // 1
myCountUpA(); // 2
myCountUpB(); // 1
myCountUpA(); // 3
myCountUpB(); // 2

上記のように実行すると、AとBはそれぞれ独立してカウントアップしています。

この結果から、counter変数はAとBの関数内に、それぞれ独立して保持されていることが分かります。

イメージとしては左手にAというカウンター、右手にBというカウンターをそれぞれ持っていて、必要なタイミングでカウントしている感じです。(そのままですが、、、笑)
AとBのカウンターは機能は同じですが別物なので、カウントは別々に行われます。こんなイメージです。

長くなりましたが、クロージャを使うことでプライベート変数を保持した関数を作ることができました。

続いて、動的な関数を生成する関数を見ていきましょう。

②動的な関数を生成する関数

ここでは、倍数を表示してくれる関数を生成してみたいと思います。

function multipleFactory(init){ // 関数を生成する親関数

    return function(val){  // returnで無名関数を返す
        console.log(init * val);
    }
}

const multiple3 = multipleFactory(3); // 3の倍数を表示する関数
const multiple5 = multipleFactory(5); // 5の倍数を表示する関数
const multiple10 = multipleFactory(10); // 10の倍数を表示する関数

multiple3(4); // 12
multiple5(5); // 25
multiple10(5); // 50

基本的な部分は先ほどの①の例と変わりません。

違う点は親関数と子関数にそれぞれ引数を持たせたことと、親関数を複数実行している点です。

関数を生成する関数と言われると難しく感じるかもしれませんが、行っているのは親関数を複数回呼び出しているだけです。

親関数を複数実行すると、その度に関数が返ってくる為、新しく関数が作られることになります。

しかし、ただ新しい関数を作っただけでは実行結果は全て同じになります。

なので、異なる値を引数として与えることで、例えば、3の倍数を返す関数、5の倍数を返す関数、と言った具合に、動的に異なる値を返す関数を作ることができるわけです。

このように、クロージャと引数を利用して、動的な関数を生成する関数を作ることができます。

まとめ

ここまでお読みいただきありがとうございます。
クロージャに関しては色々な言葉で説明がされているので混乱しましたが、活用例と重要ポイントに関してはおおよそ理解できたと思います。
至らない点がありましたらご指摘いただけると幸いです。

参考

https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures
https://qiita.com/Yametaro/items/7a4521e23520947cc43e
https://qiita.com/manten120/items/bea6686e6021c6254596
https://meetup-jp.toast.com/923

TakanoriOkawa
JavaScript・Vue.jsを勉強中です。学んだことをアウトプットしていきます。至らない点がありましたらご指摘いただけると嬉しいです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away