基本的なJavaScript擬似クラスを理解しています:
function Foo(bar) {
this._bar = bar;
}
Foo.prototype.getBar = function() {
return this._bar;
};
var foo = new Foo('bar');
alert(foo.getBar()); // 'bar'
alert(foo._bar); // 'bar'
カプセル化をエミュレートできるモジュールパターンも理解しています。
var Foo = (function() {
var _bar;
return {
getBar: function() {
return _bar;
},
setBar: function(bar) {
_bar = bar;
}
};
})();
Foo.setBar('bar');
alert(Foo.getBar()); // 'bar'
alert(Foo._bar); // undefined
しかし、これら両方のパターンには非OOPのようなプロパティがあります。前者はカプセル化を提供しません。後者はインスタンス化を提供しません。両方のパターンを変更して、疑似継承をサポートできます。
私が知りたいのは、許可するパターンがあるかどうかです:
これはどうですか :
var Foo = (function() {
// "private" variables
var _bar;
// constructor
function Foo() {};
// add the methods to the prototype so that all of the
// Foo instances can access the private static
Foo.prototype.getBar = function() {
return _bar;
};
Foo.prototype.setBar = function(bar) {
_bar = bar;
};
return Foo;
})();
そして今、インスタンス化、カプセル化、継承があります。
しかし、まだ問題があります。 private
変数は、static
のすべてのインスタンスで共有されるため、Foo
です。クイックデモ:
var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'b' :(
より良い方法は、プライベート変数の規則を使用することです。プライベート変数はすべてアンダースコアで始める必要があります。この規則は広く知られており、広く使用されているため、別のプログラマーがコードを使用または変更し、アンダースコアで始まる変数を見ると、それはプライベートであり、内部使用のみであり、変更しないことを知っています。
これは、この規則を使用した書き換えです。
var Foo = (function() {
// constructor
function Foo() {
this._bar = "some value";
};
// add the methods to the prototype so that all of the
// Foo instances can access the private static
Foo.prototype.getBar = function() {
return this._bar;
};
Foo.prototype.setBar = function(bar) {
this._bar = bar;
};
return Foo;
})();
これでインスタンス化と継承ができましたが、規約を優先してカプセル化を失いました:
var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'a' :)
alert(b.getBar()); // alerts 'b' :)
しかし、プライベート変数はアクセス可能です:
delete a._bar;
b._bar = null;
alert(a.getBar()); // alerts undefined :(
alert(b.getBar()); // alerts null :(
あなたが探しているのは「Revealing Prototype Pattern」だと思います。
Dan Wahlinには素晴らしいブログ投稿があります: http://weblogs.asp.net/dwahlin/archive/2011/08/03/techniques-strategies-and-patterns-for-structuring-javascript-code-revealing- prototype-pattern.aspx
さらに、これと他の関連するJavaScript構造に関するPluralsightコース: http://pluralsight.com/training/courses/TableOfContents?courseName=structuring-javascript&highlight=dan-wahlin_structuring-javascript-module1!dan-wahlin_structuring-javascript -module2!dan-wahlin_structuring-javascript-module5!dan-wahlin_structuring-javascript-module4!dan-wahlin_structuring-javascript-module3#structuring-javascript-module1
クロージャーはあなたの友達です!
次の小さな関数をトップレベルの名前空間に追加するだけで、OOPの準備が整いました。
_function clazz(_class, _super) {
var _prototype = Object.create((_super || function() {}).prototype);
var _deps = Array.isArray(_class) ? _class : [_class]; _class = _deps.pop();
_deps.Push(_super);
_prototype.constructor = _class.apply(_prototype, _deps) || _prototype.constructor;
_prototype.constructor.prototype = _prototype;
return _prototype.constructor;
}
_
上記の関数は、指定されたクラスのプロトタイプと最終的な親コンストラクタを単に結び付け、インスタンス化の準備ができた結果のコンストラクタを返します。
これで、静的、インスタンス、パブリック、プライベートのプロパティとメソッドを備えた数行のコードで、基本クラス(つまり{}を拡張する)を最も自然に宣言できます。
_MyBaseClass = clazz(function(_super) { // class closure, 'this' is the prototype
// local variables and functions declared here are private static variables and methods
// properties of 'this' declared here are public static variables and methods
return function MyBaseClass(arg1, ...) { // or: this.constructor = function(arg1, ...) {
// local variables and functions declared here are private instance variables and methods
// properties of 'this' declared here are public instance variables and methods
};
});
_
クラスを拡張しますか?さらに自然に:
_MySubClass = clazz(function(_super) { // class closure, 'this' is the prototype
// local variables and functions are private static variables and methods
// properties of this are public static variables and methods
return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
// local variables and functions are private instance variables and methods
_super.apply(this, arguments); // or _super.call(this, arg1, ...)
// properties of 'this' are public instance variables and methods
};
}, MyBaseClass); // extend MyBaseClass
_
つまり、親クラスのコンストラクターをclazz関数に渡し、_super.call(this, arg1, ...)
を子クラスのコンストラクターに追加します。これにより、必要な引数を使用して親クラスのコンストラクターが呼び出されます。標準の継承スキームと同様に、親コンストラクター呼び出しは、子コンストラクターの最初に来る必要があります。
コンストラクター内のコードからコンストラクターに簡単にアクセスする必要がある場合は、明示的にコンストラクターにthis.constructor = function(arg1, ...) {...}
またはthis.constructor = function MyBaseClass(arg1, ...) {...}
という名前を付けるか、単にコンストラクターを返すこともできます。上記のコードのようにreturn function MyBaseClass(arg1, ...) {...}
を使用します。あなたが最も快適に感じるものはどれでも。
コンストラクターから通常行うように、そのようなクラスからオブジェクトを単にインスタンス化します:myObj = new MyBaseClass();
クロージャーが、プロトタイプやコンストラクターを含むクラスのすべての機能をうまくカプセル化し、静的およびインスタンス、プライベートおよびパブリックのプロパティとメソッドに自然な名前空間を提供していることに注目してください。クラスクロージャ内のコードには制約がまったくありません。フレームワークも制約もありません。単なる古いJavascriptです。閉鎖ルール!
ああ、クラス(つまり、プロトタイプ)にシングルトンの依存関係(サービスなど)を挿入する場合、clazz
はAngularJSでこれを行います。
_DependentClass = clazz([aService, function(_service, _super) { // class closure, 'this' is the prototype
// the injected _service dependency is available anywhere in this class
return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
_super.apply(this, arguments); // or _super.call(this, arg1, ...)
// the injected _service dependency is also available in the constructor
};
}], MyBaseClass); // extend MyBaseClass
_
上記のコードが示すように、シングルトンをクラスに注入するには、クラスクロージャーをすべての依存関係を持つ配列の最後のエントリとして配置します。また、対応するパラメーターを、クラスクロージャーの__super
_パラメーターの前に、配列と同じ順序で追加します。 clazz
は、配列からの依存関係を引数としてクラスクロージャーに注入します。依存関係は、コンストラクタを含むクラスクロージャ内のどこからでも利用できます。
実際、依存関係はプロトタイプに注入されるため、オブジェクトがクラスからインスタンス化される前であっても、静的メソッドで使用できます。これは、アプリまたはユニットテストとエンドツーエンドテストの接続に非常に強力です。また、コンストラクタにシングルトンを注入する必要がなくなります。そうしないと、コンストラクタのコードが不必要に破壊されてしまいます。
このフィドルを確認してください: http://jsfiddle.net/5uzmyvdq/1/
フィードバックと提案は大歓迎です!
Javascriptは確かにOOPです。常にポリモーフィズムがありますが、カプセル化またはインスタンス化のどちらかを犠牲にしなければなりません。これは、遭遇した問題です。
これを試して、オプションをブラッシュアップしてください。 http://www.webmonkey.com/2010/02/make_oop_classes_in_javascript/ ブックマークした古い質問: JavaScriptはオブジェクト指向ですか?
JavaScriptクラスはECMAScript 6で導入され、JavaScriptの既存のプロトタイプベースの継承に対する構文上の砂糖です。クラス構文は、JavaScriptに新しいオブジェクト指向の継承モデルを導入していません。 JavaScriptクラスは、オブジェクトを作成して継承を処理するためのはるかに単純で明確な構文を提供します。
このリンクで詳細を見ることができます Mozilla Community
多くのJSクラスの問題の1つは、フィールドとメソッドを保護していないことです。つまり、それを使用しているユーザーが誤ってメソッドを置き換える可能性があります。たとえば、コード:
function Class(){
var name="Luis";
var lName="Potter";
}
Class.prototype.changeName=function(){
this.name="BOSS";
console.log(this.name);
};
var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
console.log("replaced");
};
test.changeName();
test.changeName();
出力されます:
ugly
BOSS
replaced
replaced
ご覧のとおり、changeName関数はオーバーライドされます。次のコードは、クラスのメソッドとフィールドを保護し、ゲッターとセッターを使用してそれらにアクセスし、これを他の言語で見られる「通常の」クラスにします。
function Class(){
var name="Luis";
var lName="Potter";
function getName(){
console.log("called getter");
return name;
};
function setName(val){
console.log("called setter");
name = val
};
function getLName(){
return lName
};
function setLName(val){
lName = val;
};
Object.defineProperties(this,{
name:{
get:getName,
set:setName,
enumerable:true,
configurable:false
},
lastName:{
get:getLName,
set:setLName,
enumerable:true,
configurable:false
}
});
}
Class.prototype.changeName=function(){
this.name="BOSS";
};
Object.defineProperty(Class.prototype, "changeName", {
writable:false,
configurable:false
});
var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
console.log("replaced")
};
test.changeName();
test.changeName();
この出力:
called getter
Luis
called setter
called getter
ugly
called setter
called setter
called setter
これで、クラスメソッドをランダムな値や関数に置き換えることができなくなり、フィールドの読み取りまたは書き込みを試行するときに、ゲッターとセッターのコードが常に実行されます。
最近、この特定の主題と、さまざまなアプローチの限界について考えていました。私が思いついた最良の解決策は以下のとおりです。
おそらくメモリ使用量は犠牲になりますが、継承、インスタンス化、カプセル化の問題を解決するようです(少なくともGoogle Chrome v.24でのテストから)。
function ParentClass(instanceProperty) {
// private
var _super = Object.create(null),
privateProperty = "private " + instanceProperty;
// public
var api = Object.create(_super);
api.constructor = this.constructor;
api.publicMethod = function() {
console.log( "publicMethod on ParentClass" );
console.log( privateProperty );
};
api.publicMethod2 = function() {
console.log( "publicMethod2 on ParentClass" );
console.log( privateProperty );
};
return api;
}
function SubClass(instanceProperty) {
// private
var _super = ParentClass.call( this, instanceProperty ),
privateProperty = "private sub " + instanceProperty;
// public
var api = Object.create(_super);
api.constructor = this.constructor;
api.publicMethod = function() {
_super.publicMethod.call(this); // call method on ParentClass
console.log( "publicMethod on SubClass" );
console.log( privateProperty );
}
return api;
}
var par1 = new ParentClass(0),
par2 = new ParentClass(1),
sub1 = new SubClass(2),
sub2 = new SubClass(3);
par1.publicMethod();
par2.publicMethod();
sub1.publicMethod();
sub2.publicMethod();
par1.publicMethod2();
par2.publicMethod2();
sub1.publicMethod2();
sub2.publicMethod2();