ECMAScriptには「クラス」という概念が
内容:
1.基本コンセプト
2.クラスメンバー
2.1。 コンストラクターメソッド
2.2。 オープンクラスのメンバー
2.3。 プライベートクラスのメンバー
2.4。 保護されたクラスメンバー
2.5。 静的クラスのメンバー
2.6。 演算子=>(太い矢印)
3.継承
4.さらに読む
基本的な概念
クラスは、一般化された論理的に関連するエンティティのセットで表される特別な構文構造です。
オブジェクトは、値がオブジェクトまたはnullの可能性があるプロトタイプオブジェクトを持つ、順序付けられていないプロパティのセットです。
コンストラクターは、オブジェクトを作成および初期化するFunction型のオブジェクトです。
プロトタイプ -構造、条件、および動作の継承を実装するために使用されます。
プロトタイププロパティは、関数がコンストラクターとして使用できるように、各関数に対して自動的に作成されます。
インスタンス -クラスのインスタンスを作成します。
一般理論
CoffeeScriptは、プロトタイプクラスのパラダイムを備えた動的言語であり、クラスの操作は委任プロトタイピングのレベルで実装されます。
より正確な定義を行うために、 CoffeeScriptのクラスは、オブジェクトを操作する代替方法を定義する抽象データ型です。
クラスを定義するには、 クラス指定子を使用する必要があります。
class
ブロードキャスト結果:
(function() { function Class() {}; return Class; })();
ご覧のとおり、クラスはコンストラクター関数によって表される単なるラッパー( 構文糖 )です。
Object::toString.call class # [object Function]
クラスのインスタンスの作成(インスタンス化)は、 new演算子を使用して実行されます
class A instance = new A
new演算子をコンストラクター関数に適用すると、内部[[Construct]]メソッドがアクティブになり、オブジェクトの作成を担当します。
class A Object::toString.call new A # [object Object]
言い換えれば、クラスの定義は、インスタンス化が発生するまでオブジェクトの作成ではありません。
class A constructor: (@variable) -> A 1 variable # 1
グローバル変数で変数variableが使用可能になっていることに注意してください。
インスタンス化しない場合、クラス定義は次のコードに似ています。
A = (variable) -> @variable = variable A 1 variable # 1
オブジェクトの作成を初期化した後、コンストラクター内のこれは作成されたオブジェクトを指します。
class A constructor: (@property) -> object = new A 1 object.property # 1 property # undefined
ご覧のとおり、 これはもはやグローバルオブジェクトを指しておらず、 プロパティ変数は未定義です。 クラス指定子のないコードにも同じことが当てはまります。
A = (@property) -> object = new A 1 object.property # 1 property # undefined
実際、これらのオプションに違いはありません。
ただし、このようなオブジェクトを作成するには、 クラス指定子を使用した構文の方が望ましいことは注目に値します。次に、その理由を説明します。
覚えているなら、関数にreturnステートメントを暗黙的に追加することについては既に言及しました。 そのような振る舞いは私たちと非常に残酷な冗談をすることができます:
A = (variable) -> method = -> variable
それは完全に無害なコードのようですか?
さて、オブジェクトを作成してみましょう。
A = (param) -> method = -> param object = new A 1 object.method() # TypeError: has no method 'method'
元気?
これが起こる理由を理解するには、ブロードキャスト結果を見てください:
var A = function(param) { return this.method = function() { return param; }; };
残念ながら、そのような驚きを回避する通常の方法はありません(現在、暗黙的にreturnステートメントを追加することを意味します)。
まず、パラメーターの前に@記号を付けて、関数を初期化パラメーターとして定義できます。
A = (@method, @param) -> object = new A (-> @param), 1 do object.method # 1
この解決策は、オブジェクトの作成時に定義されるという事実により可能です。
翻訳結果を見てみましょう:
var A, object; A = function(method, param) { this.method = method; this.param = param; }; object = new A((function() { return this.param; }), 1); object.method(); //1
注:他の関数をパラメーターとして受け取る関数、または結果として他の関数を返す関数は、 ファーストクラス関数と呼ばれます 。 この場合、関数パラメーターは関数パラメーターまたはfunargと呼ばれます。
オブジェクトのメンバーを特定できる次のメソッドは、 プロトタイプオブジェクトを使用することです 。
A = (@param) -> A::method = -> @param object = new A 1 object.method() # 1
オブジェクトのプロパティを直接、およびプロトタイプオブジェクトを通じて定義することは、同じことではないことに注意してください。
A = (@param) -> A::param = 0 object = new A 1 object.param # 1
独自のプロパティは優先度が高いため、最初に分析され、次にプロトタイプチェーンで検索が実行されます。
コンストラクター関数にreturnステートメントを暗黙的に追加する問題と、この問題を解決する方法を検討していることを思い出してください。
A = (@param) -> @method = -> @param @ object = new A 1 object.method() # 1
これは、新しく作成されたオブジェクトを指します。
返された値がオブジェクトである場合、それは新しい式の結果になることを考慮する価値があります。
A = (@param) -> @method = -> @param [] object = new A 1 object # [] Object::toString.call(object) # [object Array] object.method() # TypeError: Object has no method 'method'
したがって、 メソッドプロパティにアクセスするには、このプロパティが属するオブジェクトを明示的に返す必要があります。
A = (param) -> object = {} object.method = -> param object object = new A 1 object.method() #1
もちろん、オブジェクトへの参照がどのように返されるかは関係ありません。
A = (param) -> method: -> param object = new A 1 object.method() #1
この場合、 匿名オブジェクトへの参照はコンストラクター関数Aで返されます。
返される関数にも同じことが当てはまります。
A = (one) -> (two) -> one + two object = new A 1 object 2 # 3
ご理解のとおり、この場合のnew演算子の使用はオプションです。 そして、ネストされた関数内のこれは、グローバルオブジェクトを指します。
A = -> -> @ new A()() # global
strictモード( strictを使用 )を使用する場合 、 これはundefinedを指すことに注意してください:
A = -> 'use strict' -> @ new A()() # undefined
これが新しく作成されたオブジェクトを指すようにするには、明示的にnew演算子を追加する必要があります。
A = -> new -> @ new A() # object
次に、ネストされた関数のパラメーターを渡す方法、反対の質問があるはずです。
A = (one) -> new (two) -> one + two object = new A 1 object.constructor 2 # 3
内部CoffeeScript コンストラクター()メソッドとコンストラクター関数へのリンクを混同しないでください。これにより、オブジェクトプロトタイプへのリンクを取得できます。
A = -> new -> object = new A object.constructor:: # object object.constructor::constructor 2 # 3
この動作は、呼び出しのディスパッチにより実現されます。デリゲートポインターのチェーンに続くオブジェクトが対応するプロパティを見つけることができない場合、そのプロトタイプを参照します。 このようなオブジェクトからプロトタイプへの呼び出しのチェーンは、 プロトタイプチェーンと呼ばれます。
この動作は、 Ruby 、 Python 、 SmallTalk、およびSelfプログラマーによく知られています。
注:残念ながら、 ptototypeのようにコンストラクタのエイリアスはありませんが、近い将来、おそらくこれが考慮されるでしょう。 一部のCoffeeScript方言では、これは既に実装されています。 たとえば、 cocoでは、 コンストラクタという語はコロン( .. )に置き換えることができます。
@..:: # this.constructor.prototype
クラスのメンバー
コンストラクター()メソッド
クラスコンストラクターは、クラス本体で定義され、オブジェクトのメンバーを初期化するように設計された特別なコンストラクター()メソッドです。
class A A::property = 1 object = new A object.property # 1
この例では、 Aという名前のクラスの作成を初期化しました。
クラスの唯一のメンバーはproperty propertyです 。これは、オブジェクトAのプロトタイプに正式に配置されています。
なぜなら これは常にAを指し(また翻訳されます)、クラス定義を書き換えることは理にかなっています:
class A @::property = 1
レコードのコンパクトさにもかかわらず、このようなクラスメンバーの定義はCoffeScriptでは受け入れられません。 さらに、よりエレガントなソリューションがあります。
class A property: 1
クラス外のクラスのメンバーを決定するには、最初の形式の表記法を使用する必要があります。
class A A::property = 1
前述したように、クラスの各インスタンスには、独自のコンストラクターへのリンクがあり、そのコンストラクターには、 prototypeプロパティがあります。
このようにしてオブジェクトの初期プロトタイプへのリンクを取得したら、クラスの新しいメンバーを定義できます。
class A object1 = new A object1.constructor::property = 1 object2 = new A object2.property # 1 object2.constructor is A # true
プロトタイプにいくつかのプロパティを追加する必要がある場合、「一括」にすることは理にかなっています。
class A A:: = property: 1 method: -> @property object2 = new A object2.method() # 1
ただし、この場合、 コンストラクタープロパティは別のオブジェクトを指します。
class A A:: = {} object = new A object.constructor is A # false
元のプロトタイプへのリンクが失われるという事実にもかかわらず、それに気づく人はほとんどいません。
class A A:: = {} object = new A object.constructor::property = 1 # ! object.property # 1
このような状況では特に注意する必要があります。クラスのインスタンスを介して新しいプロパティを追加すると、このプロパティがすべてのオブジェクトに追加されるためです!
この例をよく見てください:
class A A:: = property: 1 method: -> @property object = new A object.constructor::property = 'Oh my god!' object.method() # 1 object.property # 1 list = [] list.property # 'Oh my god!'
この動作は、 コンストラクタープロパティがObjectを指すようになったために可能になりました。
class A A:: = {} object = new A object.constructor # [Function: Object] object.constructor is Object # true
言い換えれば、それを知らなくても、次のようになりました。
Object::property = 'Oh my god!'
もちろん、自分のオブジェクトにサードパーティのメソッドを表示したい人はほとんどいません。
元のコンストラクターへの正しい参照を取得するには、明示的に再作成する必要があります。
class A A:: = constructor: A object = new A object.constructor::property = 1 object.property # 1 object.constructor is A # true
これで、プロトタイプオブジェクトへの参照が正しくなりました。
覚えているなら、 コンストラクタ()クラスメソッドを検討し始めました。 したがって、このメソッドの唯一の目的は、パラメーターを初期化することです。
class A constructor: (param) -> @param = param object = new A 1 object.param # 1
したがって、クラスコンストラクターにパラメーターを渡す必要がない場合は、 コンストラクター()メソッドを省略するのが賢明です。
これはイデオロギー的であるため、コンストラクターは値を返さないでオーバーロードできません( CoffeeSciptは演算子と関数をオーバーロードしません)。
コンストラクター関数にパラメーターを渡すことはかなり一般的な操作であるため、 CoffeeScriptには特別な構文があります。
class A constructor: (@param) -> object = new A 1 object.param # 1
ブロードキャストの結果を見てみましょう。
var A, object; A = (function() { function A(param) { this.param = param; } return A; })(); object = new A(1); object.param; //1
ご覧のとおり、 paramはオブジェクトAの直接のプロパティです。 いつでも変更したり削除したりできます。
class A constructor: (@param) -> object = new A 1 object.param = 2 object.param # 2
この場合、クラスの特定のインスタンスのプロパティの値を再定義しましたが、他のインスタンスには影響しません。
次に、 CoffeeSript言語の一部ではないアクセスレベル修飾子の実装を見ていきます(多くの人がそう考えていますが)。
クラスのパブリックメンバー(パブリック)
多くのオブジェクト指向言語では、 カプセル化は、 public 、 private 、 protected、およびある程度staticのような修飾子の使用によって決定されます 。 CoffeeScriptはこれらの目的に対して少し異なるアプローチを持っています。私はこの問題をオープンメンバー( パブリック )で検討し始めることを提案します。
クラスのすべてのパブリックメンバーは、先行文字@および/またはthisなしで、連想表記で記述されます 。
class A constructor: (@param) -> method: -> @param object = new A 1 object.method() # 1
ブロードキャスト結果:
var A, object; A = (function() { function A(param) { this.param = param; } A.prototype.method = function() { return this.param; }; return A; })(); object = new A(1); object.method(); //1
翻訳結果は、クラスの開いているメンバーがオブジェクトAのプロトタイプに追加されることを示しています。
したがって、クラスメンバーへのアピールは、他のオブジェクトと同様に技術的に実行されます。
class A property: 1 method: -> @property object = new A object.method() # 1
プライベートクラスメンバー(プライベート)
クラスのプライベートメンバーでは、メンバーへのアクセスは、このメンバーが定義されているクラスのメソッドからのみ許可されます。 クラスの相続人はプライベートメンバーにアクセスできません。
閉じたクラスメンバーはリテラル表記で記述されます。
class A constructor: (@param) -> property = 1 # private method: -> property + @param object = new A 1 object.method() # 2 object.property # undefined
技術的には、クラスのプライベートメンバーは通常のローカル変数です。
var A, object; A = (function() { var property; function A(param) { this.param = param; } property = 1; A.prototype.method = function() { return property + this.param; }; return A; })(); object = new A(1); object.method(); object.property; # 2
現在、プライベートメンバーの実装は非常に限られています。 特に、クラスのプライベートメンバーは、クラス外で定義されたメンバーにはアクセスできません。
class Foo __private = 1 Foo::method = -> try __private catch error 'undefined' object = new Foo object.method() #undefined
これが起こる理由を理解するには、ブロードキャストの結果を見てください。
var A; A = (function() { var __private; function A() {} __private = 1; return Foo; })(); A.prototype.method = function() { try { return __private; } catch (error) { return 'undefined'; } };
確かにあなたはすでに質問を持っています:なぜ外部関数の定義をコンストラクター関数に入れられないのですか?
実際、クラスメンバーの定義は異なるファイルにある可能性があるため、これでは問題は解決しません。
この問題を部分的に解決するには、非常に簡単な方法があります。
class A constructor: (@value) -> privated = (param) -> @value + param __private__: (name, param...) -> eval(name).apply @, param if !@constructor.__super__ A::method = -> @__private__ 'privated', 2 class B extends A B::method = -> @__private__ 'privated', 2 object = new A 1 object.method() # 3 object = new B 1 object.method() # undefuned object.privated # undefuned
ご覧のとおり、 privatedクラスのメンバーは、基本クラスのメンバーのみが利用できます。
必要なのは、基本クラスで次のメソッドを定義することだけです。
__private__: (name, param...) -> eval(name).apply @, param if !@constructor.__super__
しかし、1つの問題があります。プライベートプロパティは__private__メソッドから直接利用できます。
object.__private__ 'privated', 2 # 3
小さな修正を考慮して(これに注意を払って解決策を提案してくれたnayjestに感謝します)、この質問も閉じることができます:
__private__: (name, param...) -> parent = @constructor.__super__ for key, value of @constructor:: allow = on if arguments.callee.caller is value and not parent eval(name).apply @, param if alllow
この実装の利点は次のとおりです。
+シンプルさと効率
+既存のコードでの簡単な実装
欠点の:
-eval関数とarguments.callee.callerを使用する
-追加の「レイヤー」 __private__
-実際の実用的価値の欠如
-このメソッドの列挙、変更、削除を制御する記述子属性は、 __ private__メソッドでは設定されません。
実装の欠陥に関する最後の段落は、次のように修正できます。
Object.defineProperty @::, '__private__' value: (name, param...) -> eval(name).apply @, param if !@constructor.__super__
これで、 __ private__メソッドは for-ofループによって列挙されなくなり、変更および削除できなくなりました。 最後の例を見てみましょう:
class A constructor: (@value) -> privated = (param) -> @value + param Object.defineProperty @::, '__private__' value: (name, param...) -> parent = @constructor.__super__ for key, value of @constructor:: allow = on if arguments.callee.caller is value and not parent eval(name).apply @, param if allow A::method = -> @__private__ 'privated', 2 class B extends A B::method = -> @__private__ 'privated', 2 object = new A 1 object.method() # 3 object = new B 1 object.method() # undefuned object.privated # undefuned i for i of object # 3, value, method
保護されたクラスメンバー(保護された)
保護されたクラスメンバは、基本クラスとその子孫のメソッド内でのみ使用できます。
正式には、 CoffeeScriptには保護されたクラスメンバーを定義するための特別な構文はありません。 それでも、同様の機能を独立して実装できます。
class A constructor: (@value) -> protected = (param) -> @value + param __protected__: (name, param...) -> parent = @constructor.__super__ for key, value of @constructor:: allow = on if arguments.callee.caller is value eval(name).apply @, param if allow A::method = -> @__protected__ 'privated', 2 class B extends A B::method = -> @__protected__ 'privated', 2 object = new A 1 object.method() # 3 object = new B 1 object.method() # 3 object.protected # undefuned
ご覧のとおり、このソリューションのアーキテクチャは、クラスのプライベートメンバーに関する問題のソリューションとほぼ同じであるため、記述子の属性にも同じ問題があります。 最終決定は次のとおりです。
Object.defineProperty @::, '__private__' value: (name, param...) -> parent = @constructor.__super__ for key, value of @constructor:: allow = on if arguments.callee.caller is value eval(name).apply @, param if allow
これがこの問題の唯一の解決策とはほど遠いこと、実装を理解するための最も普遍的なものであることは注目に値します。
静的クラスメンバー(静的)
静的クラスメンバー:
- @またはthisが前に付きます
-単一のコピーにのみ存在できます
-クラスの非静的メンバーの場合、基本クラスのプロトタイプオブジェクトを介してのみ使用可能
静的クラスメンバーを定義する例を考えてみましょう。
class A @method = (param) -> param A.method 1 # 1
次に、クラスの静的メンバーを作成する可能な(最も適切な)形式を見てみましょう。
@property: @ @property = @ this.property = @ this.constructor::property = 1 @constructor::property = 1 Class.constructor::property = 1
気づいたら、 @記号の使用はより普遍的です。
クラスの他の非静的メンバーへのアクセスは、基本クラスのプロトタイプオブジェクトを介してのみ可能です。
class A property: 1 @method: -> @::property do A.method # 1
クラスの他の静的メンバーへのアクセスは、@シンボルまたはthisまたはクラス名を介して利用できます。
class A @property: 1 @method: -> @property + A.property do A.method # 2
特別な注意を払う価値があるもう1つのポイントは、 新しい演算子の使用です。
class A @property: 1 @method: -> @property object = new A object.method() # TypeError: Object # <A> has no method 'method'
ご覧のとおり、存在しないメソッドを呼び出すと、 TypeErrorエラータイプが発生します。
次に、クラスのインスタンスを介してクラスの静的メンバーを呼び出す正しい方法を見てみましょう。
class A @property: 1 @method: -> @property object = new A object.constructor.method() # 1
=>(太い矢印)
クラスメンバーを操作する際に重要ではないもう1つの点は、 =>演算子(太い矢印)を使用する機能です。これにより、呼び出しコンテキストを失うことがなくなります。
たとえば、これはコールバック関数の作成に役立ちます。
class A constructor: (@one, @two) -> method: (three) => @one + @two + three instance = new A 1, 2 object = (callback) -> callback 3 object instance.method # 6
call()メソッドを使用して同じ結果を得ることができます 。
class A constructor: (@one, @two) -> method: (three) -> @one + @two + three instance = new A 1, 2 object = (callback) -> callback.call instance, 3 object instance.method # 6
次に、 述語を使用して状況を見てみましょう。
class A constructor: (@splat...) -> method: (three) => @splat instance = new A 1, 2, 3, 4, 5 object = (callback, predicate) -> predicate callback() object instance.method, (callback) -> callback.filter (item) -> item % 2 # [1, 3, 5]
この例では、 Number型のパラメーターのn番目の数を使用して、クラスAの作成を初期化しました。
次に、 メソッドクラスのメンバーが、コンストラクターに渡されたパラメーターを含む配列を返しました。 その後、結果の配列が述部に渡され、2を法とする配列の値がフィルター処理されて、新しい配列が生成されました。
継承
CoffeScriptの継承はextendsステートメントを使用して行われます。
例を考えてみましょう:
class A constructor: (@property) -> method: -> @property class B extends A object = new B 1 object.method() # 1
クラスBは独自のメンバーを定義しないという事実にもかかわらず、 Aからそれらを継承します。
ここで、プロパティpropertyはクラスB内でも使用できることに注意してください。
class A constructor: (@property) -> class B extends A method: -> @property object = new B 1 object.method() # 1
ご覧のように、 extends演算子の本質は非常に単純です。2つのオブジェクト間に家族関係を確立します。
extends演算子はクラスだけでなく使用できると推測することは難しくありません。
A = (@param) -> A::method = (x) -> @param * x B = (@param) -> B extends A (new B 2).method 2 # 4
残念ながら、公式のCoffeeScriptのドキュメントは例がかなり「まばら」ですが、いつでもトランスレーターを使用して特定のコードの実装を確認できます。
コード分析の最も重要なオプション:
coffee -c file.coffee # .coffee JavaScript . coffee -p file.coffee # coffee -e 'console.log i for i in [0..5]' # coffee -t #
-n(--nodes)パラメーターには、プログラムの構造を分析するための非常に重要な値があり、 構文ツリーを返します 。
class A @method: @ class B extends A do (new B).method
coffee -n
Block Class Value "A" Block Value Obj Assign Value "this" Access "method" Value "this" Class Value "B" Value "A" Block Call Value Parens Block Op new Value "B" Access "method"
CoffeeScriptの最も完全な構文については、公式ドキュメントのnodes.coffeeセクションを参照してください。
クラスで同じ名前のメソッドを定義すると、ネイティブメソッドが継承されたメソッドをオーバーライドします。
class A constructor: -> method: -> 'A' class B extends A method: -> 'B' object = new B object.method() # B
この動作は非常に期待されているという事実にもかかわらず、 BからクラスAメソッドを呼び出す機会がまだあります。
class A A::method = -> 'A' class B extends A B::method = -> super object = new B object.method() # A
このコードは、 スーパー演算子がクラスBメソッドで返されるというわずかな例外を除き、前のコードと大差ありません。
スーパーオペレーターのタスクは、親クラスで定義されたプロパティを呼び出し、呼び出しパラメーターを初期化することです。
同時に、継承構造は重要ではなく、継承の階層チェーン内の最も近いクラスのメソッドが呼び出されます。
class A A::method = -> 'A' class B extends A B::method = -> 'B' class C extends B C::method = -> super object = new C object.method() # B, method B
メソッドが直接の親で定義されていない場合、検索は委任ポインターのチェーンをたどって続行されます 。
class A A::method = -> 'A' class B extends A class C extends B C::method = -> super object = new C 1 object.method() # 'A', method A
スーパーオペレーターは、パラメーターを取ることもできます。
class A constructor: (@param) -> A::method = (x) -> x + @param class B extends A B::method = -> super 3 object = new B 1 object.method 2 # 4 (3 + 1)
パラメーター2のメソッドが呼び出されたという事実にもかかわらず、後でこの値を3に再定義しました。
コンストラクターメソッドでスーパー演算子を定義する場合、クラスコンストラクターを初期化するパラメーターをオーバーライドできます。
class A constructor: (@param) -> A::method = (x) -> x + @param class B extends A constructor: (@param) -> super 3 object = new B 1 object.method 2 # 5 (2 + 3)
もちろん、クラスメンバーとコンストラクターでのスーパー演算子の同時使用は許可されています。
class A constructor: (@param) -> A::method = (x) -> x + @param class B extends A constructor: (@param) -> super 3 B::method = (x) -> super 4 object = new B 1 object.method 2 # 7 (3 + 4)
次に、継承インターフェースを実装するパターンの内部実装を詳細に検討することを提案します。
var A, B, object, // hasOwnProperty __hasProp = {}.hasOwnProperty; // __extends , // , , .. child parent __extends = function(child, parent) { // parent for (var key in parent) { // ( ) if (__hasProp.call(parent, key)) { // child, parent. // , // c child[key] = parent[key]; } } // - function ctor() { // constructor child this.constructor = child; } // . // parent ctor.prototype = parent.prototype; // child.prototype = new ctor(); // // super child.__super__ = parent.prototype; // return child; }; // A = (function() { function A() {}; return A; })(); // B = (function(_super) { // __extends. // B, // - A __extends(B, _super); function B() { // A B // A.apply(this, arguments); return B.__super__.constructor.apply(this, arguments); } return B; })(A);
要約すると:
-クラスを使用すると、一般化された論理的に関連するエンティティの構造をより明確に視覚化できます。
-クラスの存在により、特にクラスを以前に扱ったことがある人にとって、オブジェクトと継承を扱う際に誤解が生じる可能性があります。-ECMAScriptはアクセスレベル修飾子(public、private、およびprotected)を定義していない
という事実にもかかわらず、それらは独立して実装できます(1つの場所を介して)。-クラスの内部実装は非常に簡単です。
さらに読む
ECMA-262-3. 7.1. :
ECMA-262-3. 7.2. : ECMAScript
-
PS: , , CoffeeScript — .