web-dev-qa-db-ja.com

JavaScriptクラス

基本的な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のようなプロパティがあります。前者はカプセル化を提供しません。後者はインスタンス化を提供しません。両方のパターンを変更して、疑似継承をサポートできます。

私が知りたいのは、許可するパターンがあるかどうかです:

  • 継承
  • カプセル化(「プライベート」プロパティ/メソッドのサポート)
  • インスタンス化(それぞれが独自の状態を持つ「クラス」の複数のインスタンスを持つことができます)
45
FtDRbwLXw6

これはどうですか :

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 :(
72
gion_13
7
Joe Davis

クロージャーはあなたの友達です!

次の小さな関数をトップレベルの名前空間に追加するだけで、OOPの準備が整いました。

  • カプセル化、静的およびインスタンス、プライベートおよびパブリック変数およびメソッドを使用
  • 継承
  • クラスレベルのインジェクション(シングルトンサービスなど)
  • 制約なし、フレームワークなし、単なる古いJavascript

_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/

フィードバックと提案は大歓迎です!

4
Stephane Catala

Javascriptは確かにOOPです。常にポリモーフィズムがありますが、カプセル化またはインスタンス化のどちらかを犠牲にしなければなりません。これは、遭遇した問題です。

これを試して、オプションをブラッシュアップしてください。 http://www.webmonkey.com/2010/02/make_oop_classes_in_javascript/ ブックマークした古い質問: JavaScriptはオブジェクト指向ですか?

3
user1701047

JavaScriptクラスはECMAScript 6で導入され、JavaScriptの既存のプロトタイプベースの継承に対する構文上の砂糖です。クラス構文は、JavaScriptに新しいオブジェクト指向の継承モデルを導入していません。 JavaScriptクラスは、オブジェクトを作成して継承を処理するためのはるかに単純で明確な構文を提供します。

このリンクで詳細を見ることができます Mozilla Community

Github

1
user1938455

多くの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 

これで、クラスメソッドをランダムな値や関数に置き換えることができなくなり、フィールドの読み取りまたは書き込みを試行するときに、ゲッターとセッターのコードが常に実行されます。

0
Piacenti

最近、この特定の主題と、さまざまなアプローチの限界について考えていました。私が思いついた最良の解決策は以下のとおりです。

おそらくメモリ使用量は犠牲になりますが、継承、インスタンス化、カプセル化の問題を解決するようです(少なくとも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();
0
BrettJephson