JavaScriptのクロージャを利用したprivate staticメンバ

JavaScriptには、アクセス修飾子がないため、クラスに属するstaticプロパティやstaticメソッドを外部からアクセスできないようにすることはできません。しかし、クロージャを使うことにより、privateなstaticプロパティやstaticメソッドに似た機能を実現することができます。

クロージャとは?

まず、クロージャの簡単な例を見てみましょう。下記の例では、関数foo内でローカル変数countと関数hogeを定義し、戻り値としてhogeを返します。戻り値の関数を実行するとインクリメントされたcountの値が出力されます。

通常、関数内で定義されたローカル変数は、関数実行後には不要となるため、メモリから削除されます。しかし、関数の内部に定義された関数が親スコープの変数を参照している場合、メモリは解放されず値を保持することができます。このように関数内に定義された関数が、定義された環境のスコープを参照できる構造をクロージャと言います。

クロージャを使うことにより、ローカル変数の値を保持できるだけでなく、関数内のローカルスコープには外部からアクセスできないため、変数や関数を隠蔽することができます。

JavaScript

function foo() {
  var count = 0;
  function hoge() {
    console.log(++count);
  }
  return hoge;
}

var f = foo();
f(); // 出力:1
f(); // 出力:2
f(); // 出力:3
console.log(count); // エラー

無名関数でコンストラクタをreturnするクラス定義方法

以前の投稿「JavaScriptにおけるクラス定義」では、無名関数内に定義したクラスをwindowオブジェクトのプロパティに代入する方法を紹介しましたが、JavaScriptにはクラスを定義するための特別な構文が存在しないため、実に様々なクラス定義方法があります。下記の例では、グローバル空間に宣言した変数に無名関数内で定義したクラスのコンストラクタを戻り値として返すことにより、クラスを定義しています。

JavaScript

var Foo = (function() {
  function Foo() {
    console.log("Foo's constructor");
    var name = 'Foo';
    this.getName = function() {
      return name;
    };
  }
  Foo.prototype.publicMethod = function() {
    console.log("Foo's publicMethod");
  };
  Foo.staticMethod = function() {
    console.log("Foo's staticMethod");
  };
  return Foo;
}());

var foo = new Foo(); // 出力:Foo's constructor
console.log(foo.getName()); // 出力:Foo
foo.publicMethod(); // 出力:Foo's publicMethod
Foo.staticMethod(); // 出力:Foo's staticMethod

privateなstaticプロパティ・staticメソッド

それでは、クロージャを利用してクラスにprivateなstaticメンバの機能を実装してみます。下記の例では、無名関数のローカルスコープにprivateなstaticプロパティとして使用するcount, classNameとprivateなstaticメソッドincrementを定義しています。これらのprivate staticメンバには、無名関数内からしかアクセスすることができません。また、出力を確認するとクロージャによりローカル変数の値が保持され、private staticメンバに似た機能を実現できていることがわかります。

JavaScript

var Dog = (function() {
  // private static var
  var count = 0;
  var className = 'Dog';
  // private static method
  function increment() {
    count++;
  }
  // constructor
  function Dog(name) {
    increment();
    // instance private var
    var _name = name;
    var _id = count;
    // instance privileged method
    this.getName = function() {
      return _name;
    };
    this.getId = function() {
      return _id
    };
  }
  // prototype public method
  Dog.prototype = {
    getClassName : function() {
      return className;
    },
    greeting : function() {
      console.log("I'm " + this.getName() + ". My ID is " + this.getId() + ".");
    }
  };
  // public static method
  Dog.counter = function() {
    console.log(count);
  };
  return Dog;
}());

var poodle = new Dog('poodle');
console.log(poodle.getClassName()); // 出力:Dog
poodle.greeting(); // 出力:I'm poodle. My ID is 1.

var bulldog = new Dog('bulldog');
console.log(bulldog.getClassName()); // 出力:Dog
bulldog.greeting(); // 出力:I'm bulldog. My ID is 2.

var maltese = new Dog('maltese');
console.log(maltese.getClassName()); // 出力:Dog
maltese.greeting(); // 出力:I'm maltese. My ID is 3.

Dog.counter(); // 出力:3