47
46

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[JavaScript] 簡単なカウンターで理解するクロージャ

Last updated at Posted at 2014-09-12

用途は思いつかないですが、学習がてら『呼び出すたびに連番を返す』ようなカウンターを作ってみます。

方法①

まずは 関数オブジェクトのプロパティ で実現してみます。

function count() {
    return count.num++; // プロパティ num の値を +1 して返す
}

count.num = 1; // 初期値をセット
console.log(count()); // 1 が出力される
console.log(count()); // 2 が出力される
console.log(count()); // 3 が出力される

一応、それらしいものは実現できましたが、これだとカウンターの値が(バグなどで)外部から操作できちゃいますね。あと、初期値のセットもスマートじゃないし、複数のカウンターも作れません。

方法②

今度は、普通の オブジェクト にメソッドを定義して実現してみます。

var count = {
    num: null,
    getNum: function() {
        return this.num++;
    }
};

count.num = 1; // 初期値をセット
console.log(count.getNum()); // 1 が出力される
console.log(count.getNum()); // 2 が出力される
console.log(count.getNum()); // 3 が出力される

方法①と同じことが実現できましたが、相変わらずカウンターの値が外部から操作できることや初期値の渡し方も改善されておらず、複数のカウンターも作れません。

方法③

方法①の関数を、ちょっと改造してみます。カウンターの値を関数の ローカル変数 で保持し、 関数の 戻り値として関数 を定義しています。

var count = function(initNum) {
    var localNum = initNum;
    var getNum = function() {
        return localNum++;
    };
    return getNum;
};

var counter1  = count(1);
console.log(counter1()); // 1 が出力される
console.log(counter1()); // 2 が出力される
console.log(counter1()); // 3 が出力される

var counter2  = count(9);
console.log(counter2()); // 9 が出力される
console.log(counter2()); // 10 が出力される
console.log(counter2()); // 11 が出力される

console.log(counter1()); // 4 が出力される(2つの独立したカウンターが実現できている

カウンターの値は外部から隠蔽できており、初期値も引数として渡せ、また2つの独立したカウンターが実現できました。
ちなみにこれが(いわゆる狭義の) クロージャ ですね。

広義では関数は全てクロージャじゃん、ということかもしれませんが、JavaScript界隈でのいわゆる、ということで。

方法④

方法③でもいいのですが、やっぱ関数だと何となく概念的に分りにくいかな〜と思い コンストラクタ関数 でオブジェクトっぽく実現してみます。

var Count = function(initNum) {
    var localNum = initNum;
    this.getNum = function() {
        return localNum++;
    };
};

var counter1 = new Count(1);
console.log(counter1.getNum()); // 1 が出力される
console.log(counter1.getNum()); // 2 が出力される
console.log(counter1.getNum()); // 3 が出力される

var counter2 = new Count(9);
console.log(counter2.getNum()); // 9 が出力される
console.log(counter2.getNum()); // 10 が出力される
console.log(counter2.getNum()); // 11 が出力される

console.log(counter1.getNum()); // 4 が出力される(2つの独立したカウンターが実現できている

うん、今回のケースでいうと、こっちの方が概念的に分りやすいですね。方法③と同じように、変数の隠蔽等も実現できてます。これもクロージャと呼ぶのかな?

おまけ:方法③で、複数のメソッドを定義したい場合

戻り値を関数そのままでなく オブジェクト とすれば、色々なメソッドを追加することが出来ます。
個人的には、方法④コンストラクタ関数の this とかの影響を考えるの面倒だし(new忘れとかあると嫌だし)、メソッド呼び出し(この例でいうと getNum())の方が機能が分りやすいし、この形が一番いいかも。

var count = function(initNum) {
    var localNum = initNum;
    return {
        getNum: function() {
            return localNum++;
        },
        methodHoge: function() {
            // 何か処理
        }
    }
};

var counter1 = count(1);
console.log(counter1.getNum()); // 1 が出力される
console.log(counter1.getNum()); // 2 が出力される
console.log(counter1.getNum()); // 3 が出力される

var counter2 = count(9);
console.log(counter2.getNum()); // 9 が出力される
console.log(counter2.getNum()); // 10 が出力される
console.log(counter2.getNum()); // 11 が出力される

console.log(counter1.getNum()); // 4 が出力される(2つの独立したカウンターが実現できている

おわりに

まだ初心者なのですが、JavaScriptは理解しはじめると、面白い言語ですね^^

47
46
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
46

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?