事実、私はかつて自分のオブジェクトを作成し、継承チェーンを構築する方法を学んだことを喜んでおり、発見したことを他の発見や観察と共有することを決めました。 (=
セミナーのテキストを準備する過程で、私は自分自身のためにいくつかのことを発見し、JavaScriptが自由に使える機能に驚いたこと、そして言語を詳しく見て実験することによってのみ開発者が利用できるようになることを認めます。
「来週の金曜日まで」ずっとセミナーが延期されたという事実を利用して、私の熱意が他の誰かに役立つように、私はセミナーのテキストをネット上に公開することにしました。
テキスト全体は5つのセクションに分かれています。
- JavaスクリプトのOOP(1/5):オブジェクト
- JavaスクリプトのOOP(2/5):クラス
- JavaスクリプトのOOP(3/5):クラスのプロパティとメソッド
- JavaスクリプトのOOP(4/5):クラスの継承
- JavaスクリプトのOOP(5/5):便利なリンク
JavaスクリプトのOOP(1/5):オブジェクト
JavaScriptのすべては、実際にはオブジェクトです。 配列はオブジェクトです。 関数はオブジェクトです。 オブジェクトはオブジェクトでもあります。 それでは、オブジェクトとは何ですか? オブジェクトはプロパティのコレクションです。 各プロパティは名前と値のペアです。 プロパティ名は文字列であり、プロパティ値は文字列、数値、ブール値、またはオブジェクト(配列と関数を含む)です。
ある種の変数を定義するとき、例えば:
var s = 'hello world'; alert(typeof s); // string
実際、オブジェクトのプロパティを暗黙的に設定します。 この場合、そのようなオブジェクトはグローバルウィンドウオブジェクトになります。
alert (s == window.s); // true alert (typeof window); // object
さらに、このwindow.sプロパティはそれ自体がオブジェクトです。 定義済みのプロパティの独自のコレクションが既にあります。
alert(s.length); // 11 ( )
これらすべてにより、一見、これは普通の文字列リテラルです!
プロパティ値が関数の場合、このプロパティをオブジェクトのメソッドと呼ぶことができます。 オブジェクトのメソッドを呼び出すには、名前の後に2つの括弧()を追加するだけで十分です。 オブジェクトのメソッドが実行されると、この関数内のthis変数はオブジェクト自体を参照します。 thisキーワードを使用すると、オブジェクトメソッドはオブジェクトの他のすべてのプロパティとメソッドにアクセスできます。
var s = 'futurico'; // s window (window.s) var f = function(){ // f window (window.f) alert(this == window); // true alert(this.s); // 'futurico' } f(); // f window (window.f()) var o = {}; // o window (window.o) os = 'karaboz'; // s window.o (window.os) of = function(){ // f window.o (window.of) alert(this == o); // true alert(this.s); // 'karaboz' } of(); // f window.o (window.of())
オブジェクトは、オブジェクトと
new
キーワードを初期化するコンストラクター関数を使用して作成されます。 コンストラクター関数は、他のプログラミング言語のクラスと同じ機能を提供します。つまり、クラスのオブジェクト(インスタンス)が作成されるテンプレートを記述します。 このテンプレートは、このクラスに基づいて作成されたオブジェクトが所有するプロパティとメソッドの列挙に基づいています。 JavaScriptには、すべての組み込みデータ型用の組み込みコンストラクター関数があります。
たとえば、文字列変数を宣言する場合:
var str='karaboz';
組み込みのコンストラクター関数を暗黙的に呼び出します。
var str = new String('karaboz');
それにより、
String
クラスのオブジェクト(インスタンス)を作成します。
同じステートメントは、他のすべてのJavaScriptデータ型にも当てはまります。
// var num = 12345.6789; // var num = new Number(12345.6789); // var bul = true; // var c = new Boolean(true); // var fun = function(x){var p = x}; // var fun = new Function('x', 'var p = x'); // var arr = ['a', 'b', 'c']; // var arr = new Array('a', 'b', 'c'); // var obj = {}; // var obj = new Object();
作成直後のこれらすべてのオブジェクトには、コンストラクター関数(クラス)で定義されているすべてのプロパティとメソッドが定義されています。
alert(num.toFixed(1)); // 12345.6 alert(arr.length); // 3
実際、JavaScriptインタープリターは、前の例から見えるかもしれないよりも少し複雑です。 そのため、次のコードは2つの変数(
String
クラスのオブジェクト)の等価性を示していますが、
var str1 = 'karaboz'; var str2 = new String('karaboz'); alert(str1 == str2); // true
str1の新しいカスタムメソッドを定義しようとすると、エラーが発生します。
str1.tell = function(){ alert(this); } str1.tell(); // 'str1.tell is not a function'
同時に、str2では、期待どおりにすべてが機能します。
str2.tell = function(){ alert(this); } str2.tell(); // 'karaboz'
ただし、文字列、数値、およびブールリテラルを介して作成される変数(オブジェクト)にJavaScriptによって課されるこの制限は、関数、配列、またはオブジェクトリテラルを介して作成されるオブジェクトには適用されません。 つまり 関数、配列、またはオブジェクトを含む変数(オブジェクト)、カスタムプロパティとメソッドを直接割り当てることができます。
var s = 'futurico'; // s window (window.s) var f = function(){ // f window (window.f) alert(this == window); // true alert(this.s); // 'futurico' } f(); // f window (window.f()) fs = 'karaboz'; // s window.f (window.fs) fm = function(){ // m window.f (window.fm) alert(this == f); // true alert(this.s); // 'karaboz' } fm(); // m window.f (window.fm())
ここでは、グローバルウィンドウオブジェクトのメソッドとして作成された関数f自体が、独自のプロパティとメソッドを持つことができるオブジェクトであることが明確にわかります。
JavaスクリプトのOOP(2/5):クラス
したがって、クラスは、このクラスに基づいて作成されたオブジェクトが所有するプロパティとメソッドを記述するテンプレートです。 JavaScriptで独自のクラスを作成するには、コンストラクター関数を作成する必要があります。
// - - var Class = function(p){ alert('My name is constructor'); this.p = p; }
そして、この新しいクラスのオブジェクトを作成するには、newキーワードを使用して通常の関数として呼び出す必要があります。 この場合、コンストラクター関数内のthisキーワードは、新しく作成されたオブジェクトを指すようになります。
var o = new Class('karaboz'); alert(o); // [Object object] alert(op); // 'karaboz' - o
変数oを試すと、Class()関数への呼び出しを割り当てるだけです-新しいキーワードなしでは、オブジェクトは作成されません:
var o = Class('karaboz'); // window.Class() alert(o); // undefined, , Class() alert(window.p); // 'karaboz' - window
関数を作成すると、JavaScriptはその関数に対して空の
.prototype
プロパティを自動的に作成します。
.prototype
関数の
.prototype
記述されたプロパティとメソッドは、この関数に基づいて作成されたオブジェクトのプロパティとメソッドとして利用可能になります。 これは、オブジェクトを作成するテンプレート(クラス)を記述するための基礎です。
Class.prototype.method = function(){ alert('my name is .method'); }
これで、このメソッドをオブジェクト自体のメソッドとして呼び出すことができます。
o.method(); // !
オブジェクトのプロパティが呼び出されると、最初にオブジェクト自体で検索され、そこに表示されない場合、インタープリターはオブジェクトを作成した
.prototype
関数の
.prototype
ます。
そのため、オブジェクトを作成するとき、このオブジェクトを作成したコンストラクター関数を指す
.constructor
プロパティが既にその中に存在します。
alert(o.constructor == Class); // true
オブジェクト自体にはこのようなプロパティを定義しなかったことに注意してください。 インタープリターは、オブジェクト内の.constructorプロパティを検出せず、オブジェクトを作成したコンストラクター関数の.prototypeから取得します。 チェック:
alert(Class.prototype.constructor == Class); // true
.prototype
はコンストラクター関数に対してのみ存在し、それに基づいて作成されたオブジェクトには存在しないことに注意して
.prototype
。
alert(o.prototype); // undefined alert(o.constructor.prototype); // [Object object]
.prototype
関数へのアクセスは、ストリング、数字などのJavaScriptに組み込まれたオブジェクトを含むすべてのオブジェクトに存在します。 さらに、独自のプロパティとメソッドを作成する際の制限は既にありません(プロパティとメソッドを文字列変数(文字列リテラルを介して作成されたオブジェクト)に直接割り当てようとすると、これらの制限があります)。
var s = 'karaboz'; s.constructor.prototype.tell = function(){ alert(this); } s.tell(); // , 'karaboz'
また、これらのオブジェクトの組み込みコンストラクター関数を使用して、組み込みタイプのオブジェクトに新しいプロパティまたはメソッドを直接設定することもできます。
String.prototype.tell = function(){ alert(this); }
ところで、JavaScriptのすべてがオブジェクト(=
JavaスクリプトのOOP(3/5):クラスのプロパティとメソッド
クラス(クラスメンバー)のプロパティとメソッドは、パブリック(パブリック)、プライベート(プライベート)、特権(特権)、静的(静的)のいずれかです。
公開メンバー
Openは、オブジェクト自体の外部にあるコードによって直接読み取り、変更、削除、または追加できるプロパティとメソッドです。
パブリックプロパティは、コンストラクター関数内の
.this
を使用して設定されます。
var Class = function(p){ this.p = p; } var o = new Class('karaboz'); alert(op); // 'karaboz' op = 'mertas'; alert(op); // 'mertas'
パブリックメソッドは、
.prototype
関数を使用して定義さ
.prototype
ます。
Class.prototype.method = function(){ alert('my name is .method'); } obj.method(); // 'my name is .method' obj.method = function(){ alert('my name is .method, but I am new one!'); } obj.method(); // 'my name is .method, but I am new one!'
.method
メソッドを
obj
オブジェクトに割り当て、
.method
.prototype
関数の
.prototype
同じ名前のメソッドを変更せず、
.method
メソッドを閉じるだけで、オブジェクトに同じ名前の新しいプロパティを作成します。 つまり 新しく作成されたすべてのオブジェクトには、引き続き
.prototype
標準メソッドが
.prototype
ます。
オブジェクトに
.prototype
のメソッドを
.prototype
表示させて使用させることができます。 これを行うには、単にオブジェクト自体の
.method
プロパティを削除します。
delete o.method; o.method(); // 'my name is .method'
コンストラクター関数の
.prototype
で定義されたプロパティとメソッドは、新しく作成されたオブジェクトにコピーされません。 このクラスのすべてのオブジェクトは、同じプロパティとメソッドへのリンクを使用します。 同時に、クラスのオブジェクト(インスタンス)を作成した後も含め、プログラムの任意の時点でオープンメンバーを定義できます。
プライベートメンバー
プライベートプロパティとメソッドは、オブジェクトの外部から直接アクセスできません。 これらはクラスのコンストラクター関数で直接記述され、オブジェクトが初期化されるときに作成されます。 コンストラクター関数にパラメーターとして渡される変数、キーワードvarを使用して宣言された変数、およびコンストラクター関数内でローカルとして宣言された関数には、このようなプロパティがあります。
var Class = function(p){ var secret = p; var count = 3; var counter = function(){ count –; if(count > 0){ return true; } else { return false; } } }
secret
、
count
および
counter
メソッドは、オブジェクトが初期化されるときにオブジェクトに作成されます。 これらは、オブジェクトの外部からのコードまたはオブジェクト自体のパブリックメソッドによってアクセスできないため、クローズドと呼ばれます。 これらのプライベートプロパティの使用方法を理解するには、特権付きメソッドを使用する必要があります。
特権メソッド
特権メソッドはプライベートプロパティとメソッドにアクセスでき、オブジェクトのパブリックメソッドとその外部の両方からも利用できます。 特権メソッドを削除または書き換えることは可能ですが、変更したり、保護されている秘密を明らかにするように強制したりすることはできません。
優先メソッドは、コンストラクタでthisキーワードを使用して定義されます。
var Class = function(p){ var secret = p; var count = 3; var counter = function(){ if(count > 0){ count –; return true; } else { return false; } } this.tellSecret = function(){ if(counter()){ return secret; } else { return null; } } } var o = new Class('12345'); alert(o.tellSecret()); // '12345' alert(o.tellSecret()); // '12345' alert(o.tellSecret()); // '12345' alert(o.tellSecret()); // null // counter, // a , o.counter = function(){ return true; } alert(o.tellSecret()); // null
.tellSecret
が推奨される方法です。 最初の3つの呼び出しに対してプライベートプロパティ
secret
を返し、その後のすべての開始に対して
null
を返し
null
。
.tellSecret
がプライベートメソッド
counter
呼び出すたびに、それ自体がオブジェクトのプライベートプロパティにアクセスします。 どのコードも
.tellSecret
メソッドにアクセスできますが、これによりオブジェクトのプライベートメンバーに直接アクセスすることはできません。
.prototype
を介して作成されたパブリックメソッドとは異なり、作成された各オブジェクトに特権メソッドのコピーが作成されます。これにより、当然、より多くのメモリが消費されます。 プライベートおよび特権メンバーは、オブジェクトが初期化されるときにのみ作成され、後で変更することはできません。
静的メンバー
静的プロパティとメソッドは、コンストラクター関数自体(クラス自体)に関連付けられたプロパティとメソッドです。 したがって、これらはクラスプロパティおよびメソッドとも呼ばれます。 これらは、オブジェクトの内側と外側の両方のコードで使用できます。
var Class = function(p){ this.p = p; } Class.prototype.tell = function(word){ alert(this.p + ' ' + word + ' ' + this.constructor.p); // alert(this.p + ' ' + word + ' ' + Class.p); } Class.p = 'futurico'; var o = new Class('karaboz'); o.tell('love'); // 'karaboz loves futurico';
閉鎖
JavaScriptでは、クロージャと呼ばれるもののおかげで、閉じられた特権付きのメソッドが可能です。 クロージングは関数であり、それに加えて、それが使用する包含コンテキストからのすべての字句変数です。
function
演算子を使用するときは、常に関数ではなくクロージャーを作成します。 クロージャーは、関数がそれを作成したコンテキストの外部で使用される場合でも、このクロージャーを作成したコンテキストに存在したすべての変数の値を「記憶」します。
var createFunc = function(param){ var closureParam = param; // var returnedFunc = function(){alert(closureParam);} return returnedFunc; } var f = createFunc('karaboz');
ここで、変数
f
を見ると、これは通常の関数であり、その本体には
closureParam
パラメータがあり、
f
を囲むコンテキストのどこでも未定義であり、原則としてエラーが発生することが
closureParam
ます。
alert(f); // : function(){alert(closureParam);}
ただし、エラーは発生しません
function(){alert(closureParam);}
クロージャー効果のおかげで、それを生成したコンテキストの
closureParam
を記憶します。
f(); // 'karaboz'
上記の特権メソッド
.tellSecret
を思い出せば、その
.tellSecret
を理解できます。 このメソッドは、
.tellSecret
を作成するコンテキストで宣言されたプライベート関数
count()
とプライベートプロパティ
secret
両方を記憶します。 同時に、
count()
が
.tellSecret
内で呼び出されると、この最後の関数は、本体で使用される
count()
.tellSecret
記憶し
count()
。
JavaスクリプトのOOP(4/5):クラスの継承
クラス継承の基本原則:
- サブクラスは、常にスーパークラスで定義されたすべてのプロパティとメソッドを継承します。
- サブクラスは、継承されたプロパティとメソッドをオーバーライドし、新しいものを作成できます。これは、同じ名前のスーパークラスのプロパティとメソッドに影響を与えません。
- サブクラスは、スーパークラスのネイティブメソッドをオーバーライドする場合でも、ネイティブメソッドを呼び出すことができる必要があります。
- サブクラスオブジェクトは、作成時にのみ初期化する必要があります。
JavaScriptには、クラシッククラスの継承を作成するためのツールがありません。 代わりに、オブジェクトの
.prototype
プロパティに基づく継承があります:オブジェクトのメソッドが呼び出されると、インタープリターはオブジェクト自体のプロパティでこのメソッドを探し、そこでメソッドが見つからない場合、このオブジェクトの
.prototype
関数のプロパティ(オブジェクト)で検索を続けます。
JavaScriptのこの動作を知って、2つのクラスの継承を作成してみましょう。
var Class = function(){ // - this.className = 'Class'; } Class.prototype.method = function(){ // alert('method of ' + this.className); } var ClassSub = function(){ // - this.className = 'ClassSub'; } ClassSub.prototype = new Class(); // .prototype var objSub = new ClassSub(); // ClassSub objSub.method(); // ! 'method of ClassSub'
サブクラスがスーパークラスの
.method
継承していることが
.method
ます(独自の
.method
として実行されます)。 これはどうですか? 最初に、インタープリターは
objSub
オブジェクト
objSub
で
.method
メソッドを検索します
objSub
、当然、そこでは見つかりません。 次に、インタープリターは
ClassSub.prototype
を呼び出し、このオブジェクトのプロパティの中から
ClassSub.prototype
を探します。 繰り返しますが、何も見つかりません
ClassSub.prototype.method = function(){}
似たものを
ClassSub.prototype.method = function(){}
も設定していません。 ただし、
ClassSub.prototype
オブジェクト
ClassSub.prototype
、
Class()
コンストラクター関数から作成されます。 したがって、
ClassSub.prototype
自体に必要なプロパティが見つからない場合、インタープリターはこのオブジェクトの
.prototype
関数を呼び出します。 そして、すでにここで彼は要求されたメソッドを見つけています:
Class.prototype.method = function(){}
。
この長い議論を単純な比較で確認します。
// .method objSub .method ClassSub.prototype alert(objSub.method == ClassSub.prototype.method); // true // .method ClassSub.prototype .method Class.prototype alert(ClassSub.prototype.method == Class.prototype.method); // true
このようなプロトタイプのチェーンは、任意の長さにすることができますが、いずれにしても、インタープリターの検索は、組み込みの
Object
クラスから(明示的または暗黙的に)作成されたオブジェクトに到達した時点で終了します。
Object.prototype
で要求されたメソッドがまだ見つからない場合、エラーを返します。
Object
クラスは、JavaScriptで作成されたクラスの可能な階層の最上位にあります。
ここで、この継承されたメソッドを再定義し、同時に独自の追加メソッドでサブクラスを拡張してみましょう。 同時に、スーパークラスのメソッドが同じままであることを確認します(クラスのインスタンスを作成した後でもパブリックメソッドとプロパティを追加できることに注意してください)。
ClassSub.prototype.method = function(){ // alert('method of ' + this.className + ' but new one'); } ClassSub.prototype.methodSub = function(){ // alert('methodSub of ' + this.className); }; // objSub.method(); // 'method of ClassSub but new one' // objSub.methodSub(); // 'methodSub of ClassSub' var obj = new Class(); // Class // obj.method(); // 'method of Class' // obj.methodSub(); // 'obj.methodSub is not a function'
それで、これまでのところ、すべてがうまくいっています。 サブクラスで.methodメソッドを再定義すると、サブクラスのインスタンスがそれを実行し始めました。 同時に、スーパークラスインスタンスは同じ名前の以前のメソッドを保持していました。 サブクラスのインスタンスで正常に機能する新しいサブクラスメソッドを作成しました。 ただし、この新しいメソッドはスーパークラスのメソッドになりませんでした-スーパークラスのインスタンスはそれを認識せず、エラーをスローします。
より現実的なコードを書こうとするまでは、すべてが単純に見えます。 原則として、コンストラクター関数はオブジェクトのプロパティを定義するだけでなく、いくつかの初期化関数も実行します。 たとえば、
Animal
クラスを作成します。このクラスでは、個人の名前がパラメーターとして渡され、それぞれの新しいインスタンスが誕生時に叫びます(=
var Animal = function(name){ this.name = name; this.cry(); // } Animal.prototype.cry = function(){ alert('whoa!'); } var animal_thing = new Animal('karaboz'); // 'whoa!';
今度はインスタンスが叫ぶのではなくニャーと鳴るCatのサブクラスを作成します
var Cat = function(name){ this.name = name; this.cry(); } Cat.prototype = new Animal(); // Animal Cat.prototype.cry = function(){ // .cry alert('meow!'); } var cat_thing = new Cat('mertas');
このコードを実行すると、2つの叫び声は聞こえません(おっと!) (おっ!、おっ!、ニャー!)そして、その理由がわかります。 2番目の悲鳴は、継承Cat.prototype = new Animal()を実行した瞬間に発生します。 Animalクラスのインスタンスを意図せずに作成します(そして、誕生時に叫びます)。 つまり サブクラスのインスタンスを作成する前でも、スーパークラスのコンストラクター関数をアイドル状態で実行します!
さらに、サブクラスでは、スーパークラスのコンストラクター関数を完全に複製しました! サブコンストラクターがコンストラクター関数のパラメーターを介して渡されたプロパティをオブジェクトに割り当てること、およびこのコンストラクターに異なる方法を実行させる方法を強制することは、他の方法でもわかりません。
スーパークラスのコンストラクター関数への単一呼び出しの問題を解決する
Animal
クラスのインスタンスを作成せずに、2つのクラスのプロトタイプの平等を単に指摘するだけでしょうか? (結局、彼らがプロトタイプを介して通信するのです)。 この行を変更してみましょう。
Cat.prototype = Animal.prototype;
コードを実行すると、予想される2つの悲鳴が聞こえます! しかし、問題は解決されたようです。
Cat
サブクラスのインスタンスを作成したらすぐに、
Animal
スーパークラスの別のインスタンスを作成してみましょう
var animal_thing_new = new Animal('juks'); // 'meow!', Cat!
このインスタンスは猫級の声で叫びます! 同じ名前の親クラスメソッドを書き直したことが判明しました。 問題は、
Cat.prototype = Animal.prototype
を記述すると、参照によってAnimal.prototypeオブジェクトをCat.prototypeオブジェクトに渡すことです(オブジェクトが変数に割り当てられている場合は常にそうです)。 したがって、最初の変更が不当に行われると、2番目の変更が行われます。を書いたとき
Cat.prototype = new Animal()
、
Cat.prototype
新しいオブジェクトを作成しました。プロパティを変更しても
.prototype
、オブジェクトコンストラクター関数自体のプロパティには影響しませんでした。
親クラスのインスタンスを作成せずに、少し異なる方法で継承を実装してみましょう。
.prototype
すべてのプロパティとメソッドを
.prototype
スーパークラスからサブクラスにコピーしてみましょう。問題の行を次のように書き直します。
for (var prop in Animal.prototype){ Cat.prototype[prop] = Animal.prototype[prop]; }
, , .. ! ? , , . — . , , ! .
:
.prototype
,
.prototype
, - . :
var Empty = function(){}; // - Empty.prototype = Animal.prototype; Cat.prototype = new Empty();
Cat.prototype
Empty
. , -
Empty()
.
Cat.prototype
Cat.prototype
-
Animal
.
Cat
,
Cat.prototype
, -
Cat.prototype (== new Empty())
Empty.prototype
,
Animal.prototype
-
:
var Cat = function(name){ Animal.apply(this, arguments); }
つまり
Cat
-
Animal
new Cat()
. , , — .
. , ,
.constructor
,
.prototype.constructor
-. , :
Cat.prototype = new Empty()
,
Cat.prototype
.
(new Cat()).constructor
,
Cat.prototype.constructor
,
(new Empty().constructor)
Empty.prototype.constructor ( == Animal.prototype.constructor)
. つまり
.constructor
- , ! . , :
var Cat = function(name){ this.constructor.apply(this, arguments); }
, , ..
.constructor
- , . , : , , :
var Empty = function(){}; // - Empty.prototype = Animal.prototype; Cat.prototype = new Empty(); Cat.prototype.constructor = Cat; // - Cat.superClass = Animal; // - , - : var Cat = function(name){ Cat.superClass.apply(this, arguments); }
, , :
Cat.prototype.cry = function(){ Cat.superClass.prototype.cry.apply(this, arguments); alert('one more cat was born'); }
.
.prototype
-
Function
. , , .. .
// Function.prototype.inheritsFrom = function(superClass) { var Inheritance = function(){}; Inheritance.prototype = superClass.prototype; this.prototype = new Inheritance(); this.prototype.constructor = this; this.superClass = superClass; } // - var Class = function(){} // Class.prototype.method = function(){}; // - var ClassSub = function(){ ClassSub.superClass.apply(this, arguments); } // ClassSub.inheritsFrom(Class); // sic! // ClassSub.prototype.method = function(){ ClassSub.superClass.prototype.method.apply(this, arguments); }
Java Script (5/5):
- Private Members in JavaScript , Douglas Crockford
- Classical Inheritance in JavaScript , Douglas Crockford
- OOP in JS, Part 1: Public/Private Variables and Methods , Gavin Kistner
- OOP in JS, Part 2: Inheritance , Gavin Kistner
- Inheritance in JavaScript , Kevin Lindsey
- JavaScript, - ,
- JavaScript ,
- JavaScript ,