原文:Private Members in JavaScript
Copyright 2001 Douglas Crockford. All Rights Reserved Wrrrldwide.
JavaScriptはJavaScript: 世界で最も誤解されたプログラミング言語です。JavaScriptのオブジェクトが、プライベートなインスタンス変数およびインスタンスメソッドを持てないため情報を隠蔽する能力が欠けていると思われることがあります。しかし、これは誤解です。JavaScriptのオブジェクトは、プライベートメンバを持つことができます。その方法は以下です。
JavaScriptはオブジェクトを基礎としています。配列はオブジェクトです。関数もオブジェクトです。オブジェクトもオブジェクトです。それならば、オブジェクトとは何でしょう?オブジェクトは名前と値のペアの集合です。名前は文字列で、値は文字列、数値、真偽値、およびオブジェクト(配列と関数を含む)です。通常、オブジェクトはハッシュテーブルとして実装されるため、素早く値を取得できます。
値が関数の場合、それはメソッドと見なせます。オブジェクトのメソッドが呼び出された時、this変数がオブジェクトにセットされます。メソッドはthis変数を通じてインスタンス変数にアクセスできます。
オブジェクトは、コンストラクタにより生成できます。コンストラクタは、オブジェクトを初期化する関数です。コンストラクタは、他言語におけるクラスの機能を提供し、静的変数と静的メソッドを含みます。
オブジェクトのメンバは、全てがパブリックメンバです。どんな関数からもメンバへアクセスでき、メンバを修正、削除することもできます。また、新しいメンバを追加することも可能です。新しいオブジェクトにメンバを追加するには、主に2つの方法があります。
通常この方法は、パブリックなインスタンス変数を初期化するのに利用されます。オブジェクトにメンバを追加するには、コンストラクタのthis変数を使います。
function Container(param) {
this.member = param;
}
そして、新しいオブジェクトを作るコードは以下です。
var myContainer = new Container('abc');
上記により、myContainer.memberに'abc'が入りました。
通常この方法は、パブリックなメソッドを追加するのに利用されます。オジェクト自身からメンバを探して見つからない場合、オブジェクトのコンストラクタのprototypeメンバからメンバが取得されます。プロトタイプのメカニズムは継承に用いられますし、またメモリの節約にも役立ちます。コンストラクタによって作られた全てのオブジェクトにメソッドを追加するには、コンストラクタのprototypeに関数を追加します。
Container.prototype.stamp = function (string) {
return this.member + string;
}
そして、追加したメソッドを呼び出せます。
myContainer.stamp('def')
これは'abcdef'を返します。
プライベートメンバはコンストラクタにより作られます。通常は、varにより定義される変数とコンストラクタの引数がプライベートメンバになります。
function Container(param) {
this.member = param;
var secret = 3;
var self = this;
}
このコンストラクタは、param、 secret、 selfの3つのプライベートインスタンス変数をつくります。これらの変数はオブジェクトに所属していますが、外部からアクセスすることも、オブジェクト自身のパブリックメソッドからアクセスすることもできません。プライベートインスタンス変数は、プライベートメソッドからアクセスできます。プライベートメソッドはコンストラクタの内部関数です。
function Container(param) {
function dec() {
if (secret > 0) {
secret -= 1;
return true;
} else {
return false;
}
}
this.member = param;
var secret = 3;
var self = this;
}
プライベートメソッドのdecは、secretインスタンス変数をチェックし、secretがゼロより大きい場合はsecretを1減らしtrueを返します。それ以外はfalseを返します。これは、このオブジェクトの利用を3回に制限するために使うことができます。
慣例に従い、プライベートのself変数を作ります。この変数はプライベートメソッドからオブジェクト自身を参照できるようにするために使われます。これは内部関数に不正確なthisをセットする、ECMAScript言語仕様の不具合を回避するための手段です。
パブリックメソッドからプライベートメソッドを呼ぶことはできません。プライベートメソッドを活用するためには、プリビレッジドメソッドを導入する必要があります。
プリビレッジドメソッド(特権メソッド)は、プライベート変数とプライベートメソッドにアクセスできます。そしてパブリックメソッドや外部からアクセスすることもできます。プリビレッジドメソッドを削除したり置き換えたりすることはできますが、それを改竄したり、隠蔽しているものを無理に明け渡させることはできません。
プリビレッジドメソッドは、コンストラクタの内部でthisへ割り当てられます。
function Container(param) {
function dec() {
if (secret > 0) {
secret -= 1;
return true;
} else {
return false;
}
}
this.member = param;
var secret = 3;
var self = this;
this.service = function () {
if (dec()) {
return self.member;
} else {
return null;
}
};
}
serviceはプリビレッジドメソッドです。myContainer.service()を呼び出すと、最初に呼び出されてから3回までは'abc'を返しますが、それ以降はnullを返します。serviceは、プライベート変数secretにアクセスするプライベートメソッドdecを呼び出します。serviceを他のオブジェクトやメソッドから利用することはできますが、プライベートメンバに直接アクセスすることは許しません。
パブリック、プライベート、プリビレッジドのパターンが使えるのは、JavaScriptにクロージャが備わっているためです。つまり内部関数が、外部関数が終了した後でも、常に外部関数のvarで宣言された変数とパラメータにアクセスできるということです。これはJavaScriptの非常に力強い特性です。しかし、クロージャの有効な利用法について書かれているJavaScriptプログラミングの本は現時点では存在しませんし、大部分の本はそれに触れることさえないのです。
プライベートメンバとプリビレッジドメンバはオブジェクト作成時にのみ作ることができます。パブリックメンバはいつでも追加可能です。
function Constructor(...) {
this.membername = value;
}
Constructor.prototype.membername = value;
function Constructor(...) {
var self = this;
var membername = value;
function membername(...) {...}
}
メモ : functionステートメントは、
function membername(...) {...}
以下の省略形です。
var membername = function membername(...) {...};
function Constructor(...) {
this.membername = function (...) {...};
}