JavaScriptでのクラス継承
11913 ワード
JavaScriptはクラス継承ではなくプロトタイプ継承を使用するclassのないオブジェクト向け言語です.これにより、C++やJavaなどの従来のオブジェクト向け言語を使用するプログラマーたちが困惑します.我々が見たように,JavaScriptのプロトタイプ継承はクラス継承よりも表現力が強い.
しかし、まず、私たちがなぜ継承に関心を持っているのかを明らかにしなければなりません.主に2つの原因があります.まず便利なタイプの変換です.言語システムがそれらの類似クラスの参照を自動的に変換することを望んでいる.参照オブジェクトの表示変換を要求するタイプのシステムでは、わずかなタイプのセキュリティしか得られません.これは強いタイプの言語では重要ですが、JavaScriptのようなばらばらな言語では、オブジェクト参照を強制的に変換する必要はありません.
2つ目の理由は、コードの多重化です.コードに同じメソッドを持つオブジェクトが多数存在することはよくあります.クラスは、定義のセットで作成できます.また、似たようなオブジェクトが多く存在するのも一般的で、これらのオブジェクトの中には追加と修正に関する方法に違いがあるのは少数です.クラスの継承はこれらの問題を効果的に解決できますが、プロトタイプの継承はより効果的です.
この点を説明するために、従来のclassのような言語でコードを記述できる文法糖を紹介します.次に、従来のclass言語には適用されない有用なモードを紹介します.最後に、文法糖について説明します.
クラス継承
まず,setとgetの2つのメソッドを含むParenizerクラスを追加し,それぞれvalueを設定して取得するためのメソッドと,parens内のvalueをパッケージするためのtoStringメソッドを追加した.
文法は少し違うように見えますが、よくわかるはずです.メソッドmethodは、メソッドの名前とfunctionを受け入れ、このfunctionを共通メソッドとしてクラスに追加します.
次のように書くことができます.
あなたが望むようにmyStringの値は「(0)」です.
次に、toStringメソッドでvalueが空または0の場合に「-0-」が出力される場合を除いて、別のクラス継承パラメータを作成します.
ここでのinheritsメソッドはJavaのextendsメソッドと類似しており,uberメソッドもJavaのsuperメソッドと類似している.親クラスのメソッドを呼び出すメソッドを許可します(名前を変更して字の制限を避けるだけです).
次のように書くことができます.
今回、myStringの値は「-0-」.
JavaScriptにはクラスはありませんが、プログラミングで実現できます.
多重継承
1つの関数のプロトタイプオブジェクトを操作することで、多重継承を実現することができ、複数のクラスの方法でクラスを構築することができます.ハイブリッド多重継承は実現しにくく、メソッド名の競合がある可能性があります.JavaScriptではハイブリッド多重継承を実現できますが、この例ではSwiss継承と呼ばれるより厳密な形式を使用します.
NumberValueクラスがあり、valueが特定の範囲内の数値であるかどうかをチェックし、必要に応じて例外を放出するメソッドsetValueが含まれていると仮定します.ZParenizerのsetValueとsetRangeメソッドだけが必要で、toStringメソッドは必要ありません.では、次のように書くことができます.
これにより、クラスに必要なメソッドのみが追加されます.
寄生継承
ZParenizerにはもう一つの書き方があります.Parenizerクラスから継承するだけでなく、コンストラクション関数でParenizerのコンストラクション関数を呼び出し、返される結果を渡すこともできます.このようにして,コンストラクション関数に共通メソッドを追加することなく特権メソッドを追加した.
クラスの継承はis-a関係(公有継承)であり、寄生継承はwas-a-but-now's-a関係(私有継承と公有継承)である.コンストラクション関数はオブジェクトの構造において大きな役割を果たしている.uberメソッドとsuperメソッドは依然として特権メソッドに使用できることに注意してください.
クラスの拡張
JavaScriptのダイナミック性では、既存のクラスのメソッドを追加または置換できます.methodメソッドはいつでも呼び出され、クラスのすべてのインスタンスが現在および将来このメソッドを持つことができます.クラスをいつでも拡張できます.継承にはトレーサビリティがあり、Javaのextendsと混同されないようにクラスの拡張(Class Augmentation)と呼ばれています.
オブジェクトの拡張
静的オブジェクト向け言語では、あるオブジェクトが別のオブジェクトと少し異なるようにするには、新しいクラスを定義する必要があります.JavaScriptでは、追加のクラスを定義する必要がなく、単一のオブジェクトにメソッドを追加できます.これは非常に強力です.少ないクラスを書くだけで、クラスは簡単ですから.思い出すと、JavaScriptオブジェクトはハッシュ・テーブルのようなもので、いつでも新しい値を追加することができます.値がfunctionであれば、それが方法になります.
したがって、上記の例では、ZParenizerクラスはまったく必要ありません.インスタンスを簡単に変更できます.
形式の継承を使用せずにtoStringメソッドをmyParenizerインスタンスに追加しました.言語にclassがないため、単一のインスタンスを変更できます.
Sugar
上記の例を正常に動作させるために,4つのsugar法を書いた.まずmethodメソッドで、クラスにインスタンスメソッドを追加します.
Function.prototypeに共通メソッドを追加したため、すべての関数がClass Augmentation(クラスの拡張)によって取得されました.名前と関数を受け入れ、関数のプロトタイプオブジェクトに追加します.
これはthisを返します.値を返す必要のない方法を書くと、通常thisを返します.これにより、カスケード式のプログラミングスタイルが得られます.
次に、あるクラスが別のクラスから継承されることを表すinheritsメソッドです.このメソッドは、両方のクラスが定義された後に呼び出され、クラスのメソッドを継承する前に追加されるべきです.
私たちはFunctionの拡張を続けます.親クラスのインスタンスを作成し、新しいプロトタイプとして使用します.コンストラクション関数のフィールドも変更し,uberメソッドをプロトタイプに追加した.
Uberメソッドは、自分のプロトタイプで指定したメソッドを検索します.これは、寄生継承またはオブジェクト拡張の場合に呼び出される関数です.クラスの継承を行う場合は、親クラスのプロトタイプでこの関数を見つける必要があります.Return文は関数のapplyメソッドを使用してfunctionを呼び出し、thisを表示的に設定し、配列パラメータを渡します.パラメータ(ある場合)はarguments配列から取得されます.残念ながらarguments配列は本当の配列ではないので、applyを使用して呼び出されるsliceメソッドを再び使用しなければなりません.
最後に、swissメソッドです.
Swissメソッドはargumentsを遍歴する.各nameについて、親クラスのプロトタイプから新しいクラスのプロトタイプにメンバーをコピーします.
結論
JavaScriptはclass言語のように使用できますが、かなり独特の表現力を持っています.クラスの継承,Swiss継承,寄生継承,クラスの拡張およびオブジェクトの拡張を検討した.このような大量のコードの多重化モードは,Javaよりも小さく,より簡単と考えられる言語から来ている.
クラスのオブジェクトは非常に厳格で、新しいメンバーをオブジェクトに追加するには、新しいクラスを作成するしかありません.JavaScriptでは、オブジェクトはばらばらで、簡単な値付け操作で新しいメンバーをオブジェクトに追加できます.
JavaScriptのオブジェクトは非常に柔軟であるため、クラスの階層を異なる考慮が必要です.深層の構造はあまり適用されず、逆に浅い階層の構造はより効率的で、表現力がある.
JavaScriptコードの作成に従事して14年が経ち、uber関数を使用する必要があることは発見されませんでした.Superはclassモードでは重要であるが,プロトタイプや関数式モードでは必須ではない.今から見れば、JavaScriptでclassモードをサポートしようとしたのは間違いです.
原文アドレス:Classical Inheritance in JavaScript
関連リンク:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html
しかし、まず、私たちがなぜ継承に関心を持っているのかを明らかにしなければなりません.主に2つの原因があります.まず便利なタイプの変換です.言語システムがそれらの類似クラスの参照を自動的に変換することを望んでいる.参照オブジェクトの表示変換を要求するタイプのシステムでは、わずかなタイプのセキュリティしか得られません.これは強いタイプの言語では重要ですが、JavaScriptのようなばらばらな言語では、オブジェクト参照を強制的に変換する必要はありません.
2つ目の理由は、コードの多重化です.コードに同じメソッドを持つオブジェクトが多数存在することはよくあります.クラスは、定義のセットで作成できます.また、似たようなオブジェクトが多く存在するのも一般的で、これらのオブジェクトの中には追加と修正に関する方法に違いがあるのは少数です.クラスの継承はこれらの問題を効果的に解決できますが、プロトタイプの継承はより効果的です.
この点を説明するために、従来のclassのような言語でコードを記述できる文法糖を紹介します.次に、従来のclass言語には適用されない有用なモードを紹介します.最後に、文法糖について説明します.
クラス継承
まず,setとgetの2つのメソッドを含むParenizerクラスを追加し,それぞれvalueを設定して取得するためのメソッドと,parens内のvalueをパッケージするためのtoStringメソッドを追加した.
function Parenizor(value) {
this.setValue(value);
}
Parenizor.method('setValue', function (value) {
this.value = value;
return this;
});
Parenizor.method('getValue', function () {
return this.value;
});
Parenizor.method('toString', function () {
return '(' + this.getValue() + ')';
});
文法は少し違うように見えますが、よくわかるはずです.メソッドmethodは、メソッドの名前とfunctionを受け入れ、このfunctionを共通メソッドとしてクラスに追加します.
次のように書くことができます.
myParenizor = new Parenizor(0);
myString = myParenizor.toString();
あなたが望むようにmyStringの値は「(0)」です.
次に、toStringメソッドでvalueが空または0の場合に「-0-」が出力される場合を除いて、別のクラス継承パラメータを作成します.
function ZParenizor(value) {
this.setValue(value);
}
ZParenizor.inherits(Parenizor);
ZParenizor.method('toString', function () {
if (this.getValue()) {
return this.uber('toString');
}
return "-0-";
});
ここでのinheritsメソッドはJavaのextendsメソッドと類似しており,uberメソッドもJavaのsuperメソッドと類似している.親クラスのメソッドを呼び出すメソッドを許可します(名前を変更して字の制限を避けるだけです).
次のように書くことができます.
myZParenizor = new ZParenizor(0);
myString = myZParenizor.toString();
今回、myStringの値は「-0-」.
JavaScriptにはクラスはありませんが、プログラミングで実現できます.
多重継承
1つの関数のプロトタイプオブジェクトを操作することで、多重継承を実現することができ、複数のクラスの方法でクラスを構築することができます.ハイブリッド多重継承は実現しにくく、メソッド名の競合がある可能性があります.JavaScriptではハイブリッド多重継承を実現できますが、この例ではSwiss継承と呼ばれるより厳密な形式を使用します.
NumberValueクラスがあり、valueが特定の範囲内の数値であるかどうかをチェックし、必要に応じて例外を放出するメソッドsetValueが含まれていると仮定します.ZParenizerのsetValueとsetRangeメソッドだけが必要で、toStringメソッドは必要ありません.では、次のように書くことができます.
ZParenizor.swiss(NumberValue, 'setValue', 'setRange');
これにより、クラスに必要なメソッドのみが追加されます.
寄生継承
ZParenizerにはもう一つの書き方があります.Parenizerクラスから継承するだけでなく、コンストラクション関数でParenizerのコンストラクション関数を呼び出し、返される結果を渡すこともできます.このようにして,コンストラクション関数に共通メソッドを追加することなく特権メソッドを追加した.
function ZParenizor2(value) {
var that = new Parenizor(value);
that.toString = function () {
if (this.getValue()) {
return this.uber('toString');
}
return "-0-"
};
return that;
}
クラスの継承はis-a関係(公有継承)であり、寄生継承はwas-a-but-now's-a関係(私有継承と公有継承)である.コンストラクション関数はオブジェクトの構造において大きな役割を果たしている.uberメソッドとsuperメソッドは依然として特権メソッドに使用できることに注意してください.
クラスの拡張
JavaScriptのダイナミック性では、既存のクラスのメソッドを追加または置換できます.methodメソッドはいつでも呼び出され、クラスのすべてのインスタンスが現在および将来このメソッドを持つことができます.クラスをいつでも拡張できます.継承にはトレーサビリティがあり、Javaのextendsと混同されないようにクラスの拡張(Class Augmentation)と呼ばれています.
オブジェクトの拡張
静的オブジェクト向け言語では、あるオブジェクトが別のオブジェクトと少し異なるようにするには、新しいクラスを定義する必要があります.JavaScriptでは、追加のクラスを定義する必要がなく、単一のオブジェクトにメソッドを追加できます.これは非常に強力です.少ないクラスを書くだけで、クラスは簡単ですから.思い出すと、JavaScriptオブジェクトはハッシュ・テーブルのようなもので、いつでも新しい値を追加することができます.値がfunctionであれば、それが方法になります.
したがって、上記の例では、ZParenizerクラスはまったく必要ありません.インスタンスを簡単に変更できます.
myParenizor = new Parenizor(0);
myParenizor.toString = function () {
if (this.getValue()) {
return this.uber('toString');
}
return "-0-";
};
myString = myParenizor.toString();
形式の継承を使用せずにtoStringメソッドをmyParenizerインスタンスに追加しました.言語にclassがないため、単一のインスタンスを変更できます.
Sugar
上記の例を正常に動作させるために,4つのsugar法を書いた.まずmethodメソッドで、クラスにインスタンスメソッドを追加します.
Function.prototype.method = function (name, func) {
this.prototype[name] = func;
return this;
};
Function.prototypeに共通メソッドを追加したため、すべての関数がClass Augmentation(クラスの拡張)によって取得されました.名前と関数を受け入れ、関数のプロトタイプオブジェクトに追加します.
これはthisを返します.値を返す必要のない方法を書くと、通常thisを返します.これにより、カスケード式のプログラミングスタイルが得られます.
次に、あるクラスが別のクラスから継承されることを表すinheritsメソッドです.このメソッドは、両方のクラスが定義された後に呼び出され、クラスのメソッドを継承する前に追加されるべきです.
Function.method('inherits', function (parent) {
this.prototype = new parent();
var d = {},
p = this.prototype;
this.prototype.constructor = parent;
this.method('uber', function uber(name) {
if (!(name in d)) {
d[name] = 0;
}
var f, r, t = d[name], v = parent.prototype;
if (t) {
while (t) {
v = v.constructor.prototype;
t -= 1;
}
f = v[name];
} else {
f = p[name];
if (f == this[name]) {
f = v[name];
}
}
d[name] += 1;
r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
d[name] -= 1;
return r;
});
return this;
});
私たちはFunctionの拡張を続けます.親クラスのインスタンスを作成し、新しいプロトタイプとして使用します.コンストラクション関数のフィールドも変更し,uberメソッドをプロトタイプに追加した.
Uberメソッドは、自分のプロトタイプで指定したメソッドを検索します.これは、寄生継承またはオブジェクト拡張の場合に呼び出される関数です.クラスの継承を行う場合は、親クラスのプロトタイプでこの関数を見つける必要があります.Return文は関数のapplyメソッドを使用してfunctionを呼び出し、thisを表示的に設定し、配列パラメータを渡します.パラメータ(ある場合)はarguments配列から取得されます.残念ながらarguments配列は本当の配列ではないので、applyを使用して呼び出されるsliceメソッドを再び使用しなければなりません.
最後に、swissメソッドです.
Function.method('swiss', function (parent) {
for (var i = 1; i < arguments.length; i += 1) {
var name = arguments[i];
this.prototype[name] = parent.prototype[name];
}
return this;
});
Swissメソッドはargumentsを遍歴する.各nameについて、親クラスのプロトタイプから新しいクラスのプロトタイプにメンバーをコピーします.
結論
JavaScriptはclass言語のように使用できますが、かなり独特の表現力を持っています.クラスの継承,Swiss継承,寄生継承,クラスの拡張およびオブジェクトの拡張を検討した.このような大量のコードの多重化モードは,Javaよりも小さく,より簡単と考えられる言語から来ている.
クラスのオブジェクトは非常に厳格で、新しいメンバーをオブジェクトに追加するには、新しいクラスを作成するしかありません.JavaScriptでは、オブジェクトはばらばらで、簡単な値付け操作で新しいメンバーをオブジェクトに追加できます.
JavaScriptのオブジェクトは非常に柔軟であるため、クラスの階層を異なる考慮が必要です.深層の構造はあまり適用されず、逆に浅い階層の構造はより効率的で、表現力がある.
JavaScriptコードの作成に従事して14年が経ち、uber関数を使用する必要があることは発見されませんでした.Superはclassモードでは重要であるが,プロトタイプや関数式モードでは必須ではない.今から見れば、JavaScriptでclassモードをサポートしようとしたのは間違いです.
原文アドレス:Classical Inheritance in JavaScript
関連リンク:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html