metaconstructor *パターンを使用して、これを回避できます。
function defineCtor(metaCtor) {
var proto = new metaCtor();
var ctor = proto.hasOwnProperty('constructor') ?
proto.constructor : new Function();
return (ctor.prototype = proto).constructor = ctor;
}
これで、コンストラクターを作成する(または、より正確にプロトタイプを作成してコンストラクターを返す)関数ができました。
var Widget = defineCtor(function() {
function doInternalStuff() {
// ...cant see me
}
// this function ends up on the prototype
this.getFoo = function() { return doInternalStuff(); };
});
// ...
var myWidget = new Widget();
説明
defineCtor
プロパティとして単一の無名関数を取ります。で関数を呼び出し、new
オブジェクトを作成します。オブジェクトを新しいコンストラクター関数(空の関数、または生成されたプロトタイプオブジェクト自体のconstructor
プロパティ)のプロトタイププロパティとして割り当て、その関数を返します。
これにより、内部関数が閉じられ、質問1に対処し、コンストラクターとプロトタイプのペアを設定して、質問2に対処します。
比較
defineCtor
この手法を次の2つの例と比較してください。
この例ではプロトタイプを使用しており、問題1があります。内部のものがカプセル化されていません。
function Widget(options) {
this.options = options;
}
Widget.prototype = {
getFoo: function() {
return doInternalStuff();
}
};
// How to encapsulate this?
function doInternalStuff() { /* ... */ }
この例では、すべてをコンストラクターに設定しますが、問題2があります。オブジェクトを作成するたびに、プロパティごとに新しい関数オブジェクトをインスタンス化します。
function Widget(options) {
this.options = options;
function doInternalStuff() { /* ... */ }
this.getFoo = function() {
return doInternalStuff();
};
}
この例では、上記の手法を使用して、プロトタイプを活用しながらカプセル化を提供します。
var Widget = defineCtor(function() {
// ^
// This function runs once, constructing the prototype.
// In here, `this` refers to the prototype.
// The real constructor.
this.constructor = function(options) {
// In function properties, `this` is an object instance
// with the outer `this` in its prototype chain.
this.options = options;
};
function doInternalStuff() { /* ... */ }
this.getFoo = function() { return doInternalStuff(); };
});
// ...
var myWidget = new Widget();
このアプローチにはいくつかの利点があり、他のアプローチよりもすぐに明らかなものもあります。
カプセル化を提供します。これを行うには、最初の「比較」の例をすぐに呼び出される関数でラップしますが、このアプローチは、チーム設定でよりクリーンで「強制」される可能性があります。
それは拡張可能です。「metaconstructor」関数に、「extends」、「mixin」などの関数プロパティを使用して独自のプロトタイプを与えることができます。次に、の本体の中に、のmetaCtor
ようなものを書くことができますthis.extends(BaseWidget)
。これdefineCtor
を行うためにAPIを変更する必要はありません。
これは、Google Closureコンパイラ、Eclipse、jsdocなどを「トリック」して、「メタ関数」ではなく実際のコンストラクター関数を定義していると思い込ませます。これは、特定の状況で役立つ場合があります(コードは、これらのツールが理解できる方法で「自己文書化」されています)。
*私の知る限り、「メタコンストラクター」という言葉は完全に構成されています。