47

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

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

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

8 に答える 8

73

これはどうですか :

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 :(
于 2012-09-26T21:16:38.327 に答える
7

あなたが探しているのは「RevealingPrototypePattern」だと思います。

Dan Wahlinには、すばらしいブログ投稿があります:http://weblogs.asp.net/dwahlin/archive/2011/08/03/techniques-strategies-and-patterns-for-structuring-javascript-code-revealing-prototype-pattern。 aspx

このJavaScript構造およびその他の関連するJavaScript構造に関するさらに優れたPluralsightコース:http://pluralsight.com/training/courses/TableOfContents?courseName = structure-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

于 2012-09-26T21:13:26.130 に答える
5

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

次の小さな関数を最上位の名前空間に追加するだけで、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 だけです。閉鎖のルール!

ああ、シングルトンの依存関係 (サービスなど) をクラス (つまりプロトタイプ) に注入したい場合は、clazzAngularJS 風にこれを行います。

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/

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

于 2015-03-22T09:58:39.003 に答える
3

Javascriptは確かにOOPです。あなたは常にポリモーフィズムを持っていますが、あなたが遭遇した問題であるカプセル化またはインスタンス化のいずれかを犠牲にする必要があります。

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

于 2012-09-26T21:12:36.927 に答える
2

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

このリンクで詳細を確認できますMozilla Community

ギットハブ

于 2016-01-29T14:46:46.930 に答える
0

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

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

于 2014-09-04T22:41:39.807 に答える
0

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

おそらくメモリ使用量が犠牲になりますが、継承、インスタンス化、およびカプセル化の問題を解決するようです (少なくとも 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();
于 2013-02-19T13:21:02.737 に答える
-1

このクロージャーは、インスタンス化とカプセル化を許可しますが、継承は許可しません。

function Foo(){
    var _bar = "foo";

    return {
        getBar: function() {
            return _bar;
        },
        setBar: function(bar) {
            _bar = bar;
        }
    };
};

a = Foo();
b = Foo();

a.setBar("bar");
alert(a.getBar()); // "bar"
alert(b.getBar()); // "foo"
于 2014-01-03T03:08:38.983 に答える