JavaScriptでスタイリッシュにクラスを作る - ②アクセス修飾子[public, protected, private] (インスタンスメンバ編)

# 前書き

前回からの続きです。

⇒[JavaScriptでスタイリッシュにクラスを作る - ①クラスの継承](http://qiita.com/tawatawa/items/56373c925a454d981f14)

ソースは
⇒[こちら](https://github.com/tawaship/LaClass)

前回作った「クラス機能」を元に、
まずはインスタンスメンバにアクセス修飾子(publicとか)を指定できるようにします。
(JavaScriptでもメンバって言うのだろうか？)

# 前提

privateなどの「ように」動作させること自体は難しくなく、
少し探すだけでも
`f.apply(this, arguments);`
という感じのコード例がすぐに見つかるかと思います。
(関数fをクロージャ内に定義した関数にすることで、外部スコープからアクセスできなくなる)

しかしながら、これを**必要になった分書く**、というのがスマートとは程遠い・・・。

ので、以下のような条件をつけます。

1. アクセス修飾子の定義もスマートに(重要)
2. instance.func(), instance.variable = 1 といった形でアクセスできるようにする
3. メンバはプロトタイプで定義する(インスタンス化した後に定義だとメモリがもったいない)
4. アクセス修飾子の意味合いは、よくある「呼び出し元と呼び出し先の関係性」とする


## 実装を考える

まず条件2ですが、これは単純に
「インスタンスのメンバになっていればよい」
ということです。
クラス内からは`this.fuga();`
クラス外からは`(new Hoge()).fuga();`
とアクセスできるようになっていればOKです。

条件3についてはそのままの意味で、
インスタンス化してからメンバを定義するのではなく
プロトタイプに定義して、インスタンス化した時点でメンバが定義されているようにしよう、ということです。
ただし通常どおりにprototypeを定義するだけでは
publicに該当するメンバしか作ることができませんので、何か考える必要があります。

条件4が今回のキモになります。
クラスという概念が存在しないため、
アクセス修飾子による制限を、あの手この手を駆使して構築していくことになりそうです。

## 条件1

コードの書き方としては色々ありますが、、
シンプルなものとしてはとりあえず2つほど、ぱっと思いつきます。

- `Parent.public.key = value;`
- `Parent.public(key, value);`

※同様にprotectedとprivateも

いずれも1つのプロトタイプ追加に対して1行で済みます。
どちらを選ぶかは少し悩みましたが、
keyを文字列にしなくていい前者を選ぶことにします。

通常のプロトタイプ定義と比較しても
`Parent.prototype.key = value; // 旧`
`Parent.public.key = value; // 新`

という、「prototypeオブジェクトをアクセス修飾子オブジェクトに置き換える」
ものになるので、覚えやすそうです。

早速、クラスに「アクセス修飾子オブジェクト」を作ります。

```Class.js
var Class = function(c) {
	c.extends = function(parent) {
		this.prototype = Object.create(parent.prototype);
		this.prototype.constructor = this;
		
		return this;
	};

	// 追加
	c.public    = {};
	c.protected = {};
	c.private   = {};
	
	return c;
};
```

この記法で書ける事を前提として、他の条件を満たす実装を考えていきます。

## 条件2&3

かなり近しい関係にあるのでまとめます。

アクセス権ごとのオブジェクトにメンバを登録したので、
次はこれらをプロトタイプに登録する必要があります。

### プロトタイプへの登録の仕方を考える

プロトタイプへの登録については

1. メンバ1つ作るごとに都度登録
2. メンバを作り終えた時にまとめて登録

の2パターンが考えられますが、1の方は明らかに面倒くさそう
(というかこれであればアクセス修飾子オブジェクトへの登録関数を作るパターンの方がよい)なので
ここは2を選択することにします。


```Javascript:Javascript
var Parent = Class(function() {});
Parent.public.getHoge = function() {
	return this.hoge;
}
Parent.private.hoge = 1;
```

と書いていく事になりますが、
これだけではいつParentに関する定義が終わったのかがはっきりしません。
そこで、「メンバ定義をまとめたブロック」を作り、
そのブロックを元にプロトタイプに追加していく機能を作ります。

extendsをメソッドチェーンできるように作っているので、
この機能もメソッドチェーンができるように作ります。

```Class.js
var Class = function(c) {
	c.extends = function(parent) {
		this.prototype = Object.create(parent.prototype);
		this.prototype.constructor = this;
		
		return this;
	};
	
	// 追加
	c.member = function(f) {
		f();
		
		// アクセス権ごとにプロトタイプへの登録処理・・・

		return this;
	};
	
	c.public    = {};
	c.protected = {};
	c.private   = {};
	
	return c;
};
```

```Javascript:Javascript
var Parent = Class(function() {})
.member(function() {
	Parent.public.getHoge = function() {
		return this.hoge;
	};
	
	Parent.private.hoge = 1;
});
```

早速実行してみると・・・
`Uncaught TypeError: Cannot read property 'public' of undefined`
と出てしまいます。

member関数に渡す無名関数内で`console.log(Parent)`をしてみると・・・
**undefined**だそうです。

よく考えれば当然の話で、メソッドチェーンしているため
memberが呼ばれた時点ではParentはまだ定義されていません。

かと言ってメソッドチェーンができないようにするのもスタイリッシュではないので
以下のように書き換えます。

```Javascript:Javascript
var Parent = Class(function() {})
.member(function() {
	// Parent としていた部分を this に変更
	this.public.getHoge = function() {
		return this.hoge;
	};
	
	this.private.hoge = 1;
});
```

また、member関数の実装も合わせて以下のようにします。

```Class.js
	c.member = function(f) {
		// thisにはクラスが入っている
		f.call(this, null);
		
		// アクセス権ごとにプロトタイプへの登録処理・・・

		return this;
	};
```

こうすることで、member関数に送る無名関数内では、
**this**でクラス自身にアクセスできるようになりました。スタイリッシュ。

### プロトタイプへ登録する

アクセス権オブジェクトの中には、本来プロトタイプに登録したい値が入っているので
そのままプロトタイプオブジェクトに追加するだけで良さそうです。

```Class.js
	c.member = function(f) {
		// メンバの追加処理を実行
		f.call(this, null);
		
		// publicの追加
		for (var i in this.public) {
			this.prototype[i] = this.public[i];
		}
		
		// protectedの追加
		for (var i in this.protected) {
			this.prototype[i] = this.protected[i];
		}
		
		
		// privateの追加
		for (var i in this.private) {
			this.prototype[i] = this.private[i];
		}
		
		return this;
	};
```

早速実行してみます。

```Javascript:Javascript
var Parent = Class(function Parent() {
})
.member(function() {
	// thisに変更
	this.public.getHoge = function() {
		return this.hoge;
	};
	
	this.private.hoge = 1;
});

var Child = Class(function() {})
.extends(Parent);

console.log((new Parent()).getHoge()); // 1
console.log((new Child()).getHoge()); // 1
```

Parentインスタンス・Childインスタンス共にgetHogeが呼び出せ、
getHogeからはhogeが参照できているようなので、
プロトタイプへの追加は成功しているようです。


## 条件4

ここまでで、「アクセス修飾子オブジェクトごとに分けたメンバ」を
プロトタイプへ登録する機能はできました。

が、**アクセス修飾子によってアクセス方法を限定する**ことがまだできていません。
これが条件4にあたり、かつ今回の本題になります。

### privateを作る

まずprivateとは何かを考えます。

細かい部分は省きますが、
**そのメンバが定義されたクラス内からのアクセスのみを許容するメンバ**
が一般的でしょうか？。

言い換えると
**あるメンバの呼び出し元が、メンバと同じクラスに定義されている**
です。

### そもそも呼び出し元って何よ

ここで、**呼び出し元になりえるものとは何か**を考えます。

呼び出し元ということは、呼び出し先を呼び出すコードが書かれており、
それが実行されている、ということになります。

コードを実行するには、特殊なオブジェクトである必要があります。
つまり**windowかfunctionか**です。
※windowは特殊なので別扱いにしています。

```Javascript:Javascript
var Parent = Class(function Parent() {
	// ここから呼んだら呼び出し元は function Parent(){}
})
.member(function() {
	this.public.getHoge = function() {
		// ここから呼んだら呼び出し元は function() {return this.hoge;}
		return this.hoge;
	};
	
	this.private.hoge = 1;
});

var Child = Class(function Child() {
	// ここから呼んだら呼び出し元は function Child() {}
})
.extends(Parent)
.member(function() {
	this.poke = function() {
		// ここから呼んだら呼び出し元は function() {var p = new Parent();console.log(p.hoge);}
		var p = new Parent();
		console.log(p.hoge);
	}
});

// ここから呼んだら呼び出し元は window
```

幸いにして、とある関数が呼ばれたとき、呼び出し元の関数を取得する方法が存在します。
関数が呼び出されたとき、に自動的に定義される変数`arguments`に含まれる
`arguments.callee.caller`です。

仮にwindowが呼び出し元であった場合、`arguments.callee.caller`は`null`を返すようです。
(ブラウザによって違うかも？)

**(注)仕様を見ると非標準となっているものの、表記上は主要ブラウザ全てで実装されているようです。**
**(注)argument.callee自体がstrict modeで使用できませんが、本機能は非strict mode前提です**

あとはこの`arguments.callee.caller`を見て、クラスが一意に得られれば良いわけです。
そしてご存知の通り、JavaScriptにおいては**関数もまたオブジェクト**です。

### クラスを一意に得る

まずは、「クラスを一意に得るための何か」を考えます。
ぱっと思いつくのは「クラス名」ですが、現時点ではクラス名が存在する保証がありません。

というのも、クラス＝コンストラクタと定めている関係上、
クラス名にあたるのは「コンストラクタとなる関数のnameプロパティ」とするしかないからです。

`var Parent = Class(function() {}`
というコードにおいては、Parentはあくまで「何かしらのクラスが入った変数」であり
クラス名そのものではありません。

`var Parent = Class(function Parent() {}`
とすることで、コンストラクタ関数にnameプロパティが付属されますが
無名関数を禁止するのもスタイリッシュとは言えません。

そこで、「クラスそのもの」を「クラスを一意に得るための何か」とします。
要するに**コンストラクタそのもの**です。

これらをメンバに登録していきます。

```Class.js
		// publicの追加
		for (var i in this.public) {
			this.prototype[i] = this.public[i];
			if (this.prototype[i] instanceof Function) {
				this.prototype[i]._namespace = this;
			}
		}
		
		// protectedの追加
		for (var i in this.protected) {
			this.prototype[i] = this.protected[i];
			if (this.prototype[i] instanceof Function) {
				this.prototype[i]._namespace = this;
			}
		}
		
		// privateの追加
		for (var i in this.private) {
			this.prototype[i] = this.private[i];
			if (this.prototype[i] instanceof Function) {
				this.prototype[i]._namespace = this;
			}
		}
```

メンバの呼び出し元となりえるクラスメンバは関数以外にありえないので
クラスメンバが関数の時のみ、_namespaceというプロパティを追加します。

あとは、メンバが呼び出された時に
呼び出し元の_namespaceを見て、
自身が属するコンストラクタと一致するかどうか、を確認するだけです。

自クラスのコンストラクタから呼び出された場合
　⇒`arguments.callee.caller._namespace === undefined`
　　⇒となるので、コンストラクタ自体にも\_namespaceをつける

windowから呼び出された場合
　⇒`arguments.callee.caller=== null`

この機能外のクラスインスタンスや関数などから呼び出された場合、
　⇒`arguments.callee.caller._namespace === undefined`

に注意しつつ、クラスチェック機能を作ります。


### クラスをチェックする機能を作る

次にアクセス元のクラスをチェックする機能ですが、
機能である以上、メンバが関数である必要が出てきます。

しかしメンバのすべてが関数というわけではありません。
チェック関数でラップするだけでは、値型メンバ呼び出しも
`var p = new Parent(); console.log(p.hoge())`
のように関数呼び出しする必要が出てきてしまい、何がなんだかわかりません。

オブジェクトへのアクセスは普通にできつつ、裏でチェック関数も実行したい・・・
そこで登場するのが**accessor**です


```Class.js
// privateの追加
		for (var i in this.private) {
			Object.defineProperty(this.prototype, i, (function() {
				var key = i;
				var v = self.private[i];
				if (v instanceof Function) {
					return {
						value: function() {
							if (arguments.callee.caller && arguments.callee.caller._namespace && arguments.callee.caller._namespace === self) {
								return v.apply(this, arguments);
							} else {
								throw new Error('Function \'' + key + '\' is private.');
							}
						}
					};
				} else {
					return {
						get: function() {
							if (arguments.callee.caller && arguments.callee.caller._namespace && arguments.callee.caller._namespace === self) {
								return v;
							} else {
								throw new Error('Variable \'' + key + '\' is private.');
							}
						},
						set: function(value) {
							if (arguments.callee.caller && arguments.callee.caller._namespace && arguments.callee.caller._namespace === self) {
								v = value;
							} else {
								throw new Error('Variable \'' + key + '\' is private.');
							}
						},
					};
				}
			})());
			
			if (this.private[i] instanceof Function) {
				Object.defineProperty(this.private[i], '_namespace', {
					value: this
				});
			}
		}
```

いきなりガラッと変わりましたが、主要な点は以下です。

- accessorを利用することで、値型であってもクラスチェックを行える

これはまあ見たまんまですね。
プロトタイプにはaccessorとして登録しているので、
`this.hoge = 1;`
のようなアクセスであっても、呼び出し元をチェックすることができます。

また副次的要素ですが、関数型メンバの方でも
writableを設定しない(=false)にすることで
`this.getHoge = 1;`
のような、やめてほしい再代入を防ぐことができています。

- for文の中での処理なので、ループ変数 i の取り扱いに注意する。

初歩的な話ではあるんですが・・・ひっかかったので。
定義した関数内では i がループ変数 i にバインドされているので、そのまま使うと i の最終ループ点を指してしまいます。
ので、ローカル変数 key に代入してそちらを参照するようにします。

- 値型メンバの参照時に、プロトタイプを参照しないように注意する

インスタンス化した後にメンバの値を変えた際に、次にインスタンス化した時の初期値がその値になってしまいます。
ので、ローカル変数 v に代入してそちらを参照するようにします。

- じゃあ関数以外の参照型メンバは？　このコードだとプロトタイプへの参照が入ってそうだけど。

そもそもプロトタイプに参照型を登録すると、そのプロトタイプを元にしたインスタンスは
プロトタイプに登録された**参照型の実体への参照**を持つことになります。

```Javascript:Javascript
function A(){}
A.prototype.data = {};

var a = new A();
a.data.hoge = 1;


var a2 = new A();
a2.data.fuga = 2;

console.log(a2.data); // Object {hoge: 1, fuga: 2}
```

これはJavaScriptのプロトタイプがそうなってるためなので
本機能以外でも注意すべき点ですね！

では、実行してみます。

```Javascript:Javascript
var Parent = Class(function Parent() {
	console.log(this.getHoge()); // 1
	console.log(this.getHoge2()); // 1
	console.log(this.hoge); // 1
})
.member(function() {
	this.public.getHoge = function() {
		return this.hoge
	};

	this.private.hoge = 1;
		
	this.private.getHoge2 = function() {
		return this.hoge;
	};
});

var Child = Class(function() {
	console.log(this.getHoge()); // 1
	console.log(this.getHoge2());  // Uncaught Error: Function 'getHoge2' is private.
	console.log(this.hoge); // Uncaught Error: Variable 'hoge' is private.
})
.extends(Parent);

var p = new Parent();
console.log(p.getHoge()); // 1
console.log(p.getHoge2()); // Uncaught Error: Function 'getHoge2' is private.
console.log(p.hoge); // Uncaught Error: Variable 'hoge' is private.

var c = new Child();
console.log(c.getHoge()); // 1
console.log(c.getHoge2()); // Uncaught Error: Function 'getHoge2' is private.
console.log(c.hoge); // Uncaught Error: Variable 'hoge' is private.
```

**バッチリ！**

### protectedを作る

protectedとprivateの違いは
**メンバが定義されたクラスを継承したクラスからのアクセスも許容する**
という点なので、**クラスを継承したクラス**が分かればすぐにできそう！

今回は**プロトタイプチェーン**を使っての継承システムなので、

- メンバが所属するクラス
- 呼び出し元のクラス

が分かってしまえば、プロトタイプを比較して継承関係を調べることができます。

```Javascript:Javascript
function A() {}

function B() {}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function C() {}
C.prototype = Object.create(B.prototype);
C.prototype.constructor = C;

function D() {}

console.log(A.prototype.isPrototypeOf(B.prototype)); // true
console.log(A.prototype.isPrototypeOf(C.prototype)); // true
console.log(B.prototype.isPrototypeOf(C.prototype)); // true
console.log(A.prototype.isPrototypeOf(D.prototype)); // false
console.log(A.prototype.isPrototypeOf(A.prototype)); // false, 自身との比較はfalseになることに注意

```

となるので、privateの処理における
アクセス元クラスチェックの部分に
`self.prototype.isPrototypeOf(arguments.callee.caller._namespace.prototype)`
と追加してあげることで、自クラスを継承しているクラスからのアクセスも許容する事が可能になります。

では実装して、実行してみます。

```Javascript:Javascript
var Parent = Class(function Parent() {
	console.log(this.getHoge()); // 1
	console.log(this.getHoge2()); // 1
	console.log(this.hoge); // 1
})
.member(function() {
	this.public.getHoge = function() {
		return this.hoge
	};

	
	this.private.hoge = 1;
		
	this.protected.getHoge2 = function() { // protectedに変更
		return this.hoge;
	};
});

var Child = Class(function() {
	console.log(this.getHoge()); // 1
	console.log(this.getHoge2());  // 1
	console.log(this.hoge); // Uncaught Error: Variable 'hoge' is private.
})
.extends(Parent);

var p = new Parent();
console.log(p.getHoge()); // 1
console.log(p.getHoge2()); // Uncaught Error: Function 'getHoge2' is protected.
console.log(p.hoge); // Uncaught Error: Variable 'hoge' is private.

var c = new Child();
console.log(c.getHoge()); // 1
console.log(c.getHoge2()); // Uncaught Error: Function 'getHoge2' is protected.
console.log(c.hoge); // Uncaught Error: Variable 'hoge' is private.
```

**バッチリ！！**

### publicを作る

最後にpublicですが、クラスをチェックする必要がないため
通常のprototypeへの登録とほとんど変わりません。

しかし、本機能では「メンバが関数の場合、_namespaceを登録する」という処理が必要です。
これは「呼び出す側の」関数に登録する必要があるため、
仮にpublicであっても必要な処理となります。

ですので、privateをベースにしつつ、クラスチェックを行わないようにします。

# まとめ

ということで、難しいと思われていたアクセス修飾子の概念が
いい感じに実現できてきました。

かなり間を端折ってしまっているので分かりづらかったらすみません・・・。

次回は「静的メンバ(static)へのアクセス権(public, protected, private)の付与」の予定です。
静的メンバも同じ考え方が基本にはなるのですが、
今回のインスタンスメンバとは天と地ほどの実装難易度があります・・・。

ではまた～。