読者です 読者をやめる 読者になる 読者になる

プライベート変数

JavaScriptの言語仕様(ES5)

通常、オブジェクトのプロパティには、どこからでもアクセスできる。
このままだと、状態管理が大変になってしまう。
管理が煩雑になるし、予期していない箇所で値が書き換えられる恐れがあり、バグの温床にもなる。

特定の方法でのみアクセスできる変数を作ることで、そういった状況を防ぐことが出来る。

このような変数は、プライベート変数、などと呼ばれる。

モジュールパターン

モジュールパターンと呼ばれる方法で、プライベート変数を実現できる。

具体的には、即時実行関数を利用し、クロージャを活用することで、実現する。
クロージャについては、改めて別の記事で書く予定。

var obj1 = {data:7};

// obj1.dataには、いつでも、どこからでもアクセスできてしまう
obj1.data = 99;
console.log(obj1.data); // 99

var obj2 = function(){
    var data = 7;
    return {
        getData: function(){ return data; },
        incData: function(){ data++ }
    };
}();

console.log(obj2.getData());    // 7
obj2.data = 99;
console.log(obj2.getData());    // 7

obj2.incData();
console.log(obj2.getData());    // 8

// dataは、即時実行した無名関数のローカル変数なので、外部からはアクセスできない。
// アクセス出来るのは、dataと同じスコープで作成されたgetDataとincDataのみ
// 無名関数であるため既に破棄されており、アクセス方法が追加されることもない
// アクセス出来るのは恒久的に、getDataとincDataのみ

console.log(obj2.data); // 99

これで、プライベート変数であるdataを作れた。
が、上記の例では、それとは別のobj2.dataが作成されていることに留意する必要がある。

obj2.data = 99;

を実行した際に、obj2.dataは存在しなかったため、99を格納するプロパティとして、新たに作成された。
ローカル変数dataと、obj2のプロパティであるdataは、名前が同じであるだけで、全くの別物である。

コンストラクタでのプライベート変数

上記の仕組みを即時実行関数ではなくコンストラクタに実装することで、インスタンス毎にプライベート変数を持つことが出来る。

function MyConstructor(num){
    var data = num;
    return {
        getData: function(){ return data; },
        incData: function(){ data++ }
    };
};

var obj1 = MyConstructor(10);
var obj2 = MyConstructor(70);

// 外部からはdataにアクセスできない
console.log(obj1.data); // undefined
console.log(obj2.data); // undefined

obj1.incData();

console.log(obj1.getData());    // 11
console.log(obj2.getData());    // 70

上記の例では、インスタンスが作成される度に、各インスタンスがそれぞれに、プライベート変数dataと、それにアクセスするためのメソッドを持つ。

プライベート変数の共有

先ほどの例では、インスタンス毎にプライベート変数を持っていた。
だが、即時実行関数と組み合わせることで、全てのインスタンスが同一のプライベート変数を持つようにすることも出来る。

var Student = function(){
    var grade = 1;

    function InnerStudent(name){
        this.name = name;
    };
    InnerStudent.prototype.getGrade = function(){ return grade; };
    InnerStudent.prototype.incGrade = function(){ grade++ };

    return InnerStudent;

}();

var student1 = new Student('Tom');
var student2 = new Student('Bob');

console.log(student1.name); // Tom
console.log(student2.name); // Bob

student1.incGrade();
student2.incGrade();

// gradeは全てのインスタンスからアクセス出来ることが分かる
console.log(student1.getGrade());   // 3
console.log(student2.getGrade());   // 3

// 外部からはgradeにアクセスできない
console.log(student1.grade);    // undefined

上記の例では、即時実行関数により、Studentには、InnerStudentが入っている。

InnerStudentは、自身のプロパティであるnameを持つほか、プロトタイプとして、getGrade()incGrade()を持つ。この2つのメソッドは、即時実行された無名関数のローカル変数でるgradeにアクセスしている。

プロトタイプであるため、全てのインスタンスはこの2つのメソッドを利用でき、それにより、どのインスタンスからでもgradeという同一のプライベート変数にアクセスできる。