1 さらにいくつかの基本概念 なぜこれをもう一度言うのでしょうか?
継承について説明するときに、カプセル化と密接に関連するいくつかの基本概念をすでに列挙しましたが、今日説明する基本概念は主に継承とポリモーフィズムに関連していますが、カプセル化と密接に関連する概念もあります。いくつかのつながり。
1.1 定義と代入
変数の定義とは、
var a; の形式で変数を宣言することを指します。
関数定義とは、
function a(...) {...}
の形式で関数を宣言することを指します。
var a = 1;
は 2 つのプロセスです。 1 つ目の処理は変数 a を定義することであり、2 つ目の処理は変数 a に値を代入することです。
同様に
var a = function(...) {};
最初の処理は変数 a と匿名関数を定義する処理で、2 番目の処理は匿名関数を代入する処理です。関数を変数 a に変換します。
変数定義と関数定義はスクリプト全体が実行される前に完了しますが、変数の割り当ては実行フェーズ中に完了します。
変数定義の機能は、宣言された変数にスコープを示すことだけであり、定義されていないが直接使用されている変数、または定義されているが使用されていない変数には初期値を与えません。値が割り当てられている場合、その値はすべて未定義です。
関数のスコープの宣言に加えて、関数定義では関数本体の構造も定義します。このプロセスは再帰的です。つまり、関数本体の定義には、関数本体内の変数と関数の定義が含まれます。
次の例を通してこれをより明確に理解できます:
alert(b);
var a = "a";
function b ( ) {}
var b = "b";
var c = function() {}
alert(a); ;
alert(c);
このプログラムの結果はどうなるでしょうか?次に、それを実行して、思ったものと同じかどうかを確認します。思ったものと同じであれば、上記の内容を理解したということになります。
このプログラムの結果は非常に興味深いものです。最初のalert(a)が先頭にありますが、出力される値は function a() {} であることがわかります。これは、関数定義が実際に全体で実行されていることを示しています。プログラムは以前に行われました。
もう一度 b を見ると、関数 b は変数 b の前に定義されていますが、最初のalert(b) の出力は依然として function b() {} です。これは、変数定義が変数に対して何も行っていないことを示しています。それは宣言するだけであり、関数定義を上書きしません。
最後に、c を見てください。最初のalert(c) の出力は未定義です。これは、var c = function() {} が関数 c を定義せず、変数 c と無名関数のみを定義していることを意味します。
2 番目のアラート (a) を見ると、出力が実際には a であることがわかります。これは、代入ステートメントが実行中に実際に完了したことを示しており、したがって関数 a の定義をカバーしています。
2 番目のアラート (b) はもちろん同じで、出力は b になります。これは、代入ステートメントが関数定義の前に書かれたか後に書かれたかに関係なく、同じ名前の変数に値を代入することを意味します。関数は常に関数定義を上書きします。
2 番目のアラート(c) の出力は function() {} です。これは、代入ステートメントが順番に実行され、代入された値が関数であるか他のオブジェクトであるかに関係なく、後続の代入が前の代入を上書きすることを示しています。
上記の内容を理解した後、function x(..) {…} をいつ使用するか、var x = function (…) {…} をいつ使用するかを理解する必要があると思います。
最後に、変数定義と関数定義が eval に出現する場合、それらは実行フェーズ中に完了することを思い出していただきたいと思います。したがって、絶対に必要な場合以外は eval を使用しないでください。また、eval を使用したい場合でも、その中でローカル変数やローカル メソッドを使用しないでください。
1.2 this と実行コンテキスト
以前にカプセル化について説明したときに、これについてはすでに説明しました。カプセル化の説明では、this は、this が配置されているクラスのインスタンス化されたオブジェクト自体を表します。これは本当にそうなのでしょうか?
まず次の例を見てみましょう:
コードをコピーします
コードは次のとおりです:
var x = "私はグローバル変数です!";
関数メソッド() {
アラート(x);
アラート(this.x);
}
function class1() {
// プライベートフィールド
var x = "私はプライベート変数です!";
// プライベート メソッド
function method1() {
alert(x);
アラート(this.x);
}
var method2 = メソッド;
// パブリックフィールド
this.x = "私はオブジェクト変数です!";
// パブリック メソッド
this.method1 = function() {
alert(x);
アラート(this.x);
}
this.method2 = メソッド;
// コンストラクター
{
this.method1(); // 私はプライベート変数です!
// 私はオブジェクト変数です!
this.method2(); // 私はグローバル変数です!
// 私はオブジェクト変数です!
メソッド1(); // 私はプライベート変数です!
// 私はグローバル変数です!
メソッド2(); // 私はグローバル変数です!
// 私はグローバル変数です!
method1.call(this); // 私はプライベート変数です!
// 私はオブジェクト変数です!
method2.call(this); // 私はグローバル変数です!
// 私はオブジェクト変数です!
}
}
var o = new class1();
メソッド(); // 私はグローバル変数です!
// 私はグローバル変数です!
o.method1(); // 私はプライベート変数です!
// 私はオブジェクト変数です!
o.method2(); // 私はグローバル変数です!
// 私はオブジェクト変数です!
なぜこのような結果になったのでしょうか?
まず、実行コンテキストとは何かを見てみましょう。では、実行コンテキストとは何でしょうか?
メソッドが現在実行中の場合、実行コンテキストは、オブジェクトの作成プロセス (new によって作成) が現在実行中の場合、作成されたオブジェクトが実行コンテキストになります。
メソッドが実行時に明示的にオブジェクトにアタッチされていない場合、その実行コンテキストはグローバル オブジェクト (トップレベル オブジェクト) になりますが、必ずしもグローバル オブジェクトにアタッチされるわけではありません。グローバル オブジェクトは現在の環境によって決まります。ブラウザ環境では、グローバル オブジェクトはウィンドウ オブジェクトです。
すべての関数の外で定義されたグローバル変数とグローバル関数はグローバル オブジェクトにアタッチされますが、関数内で定義されたローカル変数とローカル関数はどのオブジェクトにもアタッチされません。
実行コンテキストと変数スコープの間には関係がありますか?
実行コンテキストと変数のスコープが異なります。
関数が別の変数に割り当てられる場合、関数内で内部的に使用される変数のスコープは変更されませんが、その実行コンテキストは変数がアタッチされているオブジェクトに変更されます (変数にオブジェクトがアタッチされている場合) 。
関数プロトタイプの call メソッドと apply メソッドは、実行コンテキストを変更できますが、変数のスコープは変更しません。
上記の言葉を理解するには、次の 1 つのことだけを覚えておく必要があります。
変数のスコープは定義時に決定され、実行コンテキストが実行時に決定される間は決して変更されません。いつでも変更できます。
このようにすると、上記の例を理解するのは難しくありません。 this.method1() ステートメント (ここで述べている内容はまだ関数本体に入っていないことに注意してください) が実行されると、オブジェクトが作成され、現在の実行コンテキストは作成中のオブジェクトであるため、これは現在作成されているオブジェクトを指します。 created. の場合、 this.method1() メソッドが実行されるとき (ここでは関数本体の入力を指します)、実行中のメソッドにアタッチされるオブジェクトも作成されるオブジェクトであるため、 this.x の this も同じオブジェクトです。したがって、表示される出力は「I'm a object variable!」です。
関数method1()を実行するとき(関数本体に入った後)、method1()はclass1に定義されていますが、class1にアタッチされていません。オブジェクトは class1 によってインスタンス化されますが、そのスコープは class1 に制限されます。したがって、その関連オブジェクトは実際にはグローバル オブジェクトであるため、その中でalert(this.x) が実行されると、this.x は「私は x のグローバル変数です!」としてグローバル環境で定義した値になります。
method2()はclass1に定義されていますが、method2にmethod()を代入してもメソッドのスコープは変更されず、method2が実行されるとメソッド内で実行されます。メソッドが定義されているスコープなので、表示されるのは 2 つの I'm グローバル変数です。同様に、 this.method2() が呼び出されると、alert(x) はこの理由から I'm a global variable! を出力します。
callは実行コンテキストを変更するため、method1.call(this)とmethod2.call(this)を渡すとthis.xがI'mオブジェクト変数になります。ただし、スコープを変更することはできないため、x は call メソッドがない場合と同じままです。
後で o.method1() を実行するとき、alert(x) はこれを使用して x の実行コンテキストを指定しません。したがって、x は現在実行されている関数のスコープ内で最後に定義された変数を表します。この時の出力は I'm a private variable! です。最終的な出力は、「I'm a object variable!」です。私が言わなくても、その理由は誰もが知っていると思います。
2 継承とポリモーフィズム
2.1 カプセル化から始める
前に述べたように、カプセル化の目的はデータの隠蔽を実現することです。
しかし、より深いレベルでは、JavaScript でのカプセル化には次の利点もあります。
1. プライベート部分の実装が完全に書き換えられると、呼び出し側の動作を変更する必要がなくなります。これは、カプセル化を実現する他のオブジェクト指向言語の主な目的でもあります。
2. JavaScript では、ローカル変数とローカル関数へのアクセスが高速になるため、プライベート フィールドをローカル変数でカプセル化し、プライベート メソッドをローカル メソッドでカプセル化すると、スクリプトの実行効率が向上します。
3. JavaScript 圧縮難読化ツール (私が知る限り、現時点で最高の JavaScript 分析、圧縮、および難読化ツールは JSA です) では、ローカル変数とローカル関数名は置き換えることができますが、グローバル変数とグローバル関数名は置き換えることができません。 (実際、これは JavaScript パーサーにも当てはまります)。したがって、オープン ソースまたは非オープン ソース JavaScript プログラムの場合、プライベート フィールドとプライベート メソッドでカプセル化テクノロジを使用する場合、コードを記述するときに十分な長さの表意文字名を使用して定義してコードの読みやすさを向上させることができます。また、公開するときに、完全に圧縮して難読化できるように、いくつかの非常に短い名前 (通常は 1 文字の名前) に置き換えられます。また、帯域幅の使用量も削減され、詳細を完全に隠すことができます。
つまり、カプセル化は JavaScript にとって非常に便利です。
では、JavaScript で継承を実装する目的は何でしょうか?
2.2 継承を行う理由
他のオブジェクト指向プログラミング言語では、繰り返しコードの記述を減らすことに加えて、継承の最大の用途はポリモーフィズムを実現することです。これは、厳密に型指定された言語に特に当てはまります:
1. 厳密に型指定された言語では、2 つの型が変数の型と互換性がない限り、変数に異なる型の 2 つの値を割り当てることはできません。継承。
2. 厳密に型指定された言語では、既存の型のメソッドを直接拡張したり書き換えたりすることはできません。型を拡張するには、それを継承し、そのサブクラスで拡張して書き換えるしかありません。
したがって、厳密に型指定されたオブジェクト指向言語の場合、ポリモーフィズムの実装は継承の実装に依存します。
JavaScript 言語の場合、ポリモーフィズムを実現するために継承はそれほど重要ではありません。
1. JavaScript 言語では、任意の型の値を同じ方法で呼び出すことができます。オブジェクト上に同じ名前が付けられています。
2. JavaScript 言語では、既存の型のメソッドをプロトタイプを通じて直接拡張したり書き換えたりできます。
つまり、JavaScript における継承の主な役割は、重複したコードの記述を減らすことです。
次に説明する 2 つの継承方法は、誰もがよく知っているかもしれません。1 つはプロトタイプ継承方法で、もう 1 つは呼び出し継承方法です。これら 2 つの方法はどちらも副作用を引き起こしません。この 2 つの方法の本質と注意点を中心に説明します。
2.3 プロトタイプの継承方法
JavaScript では、各クラス (関数) がプロトタイプを持ち、クラスのインスタンス化時に、そのクラスのインスタンス化されたオブジェクトにプロトタイプのメンバーが渡されます。インスタンス化されたオブジェクトにはプロトタイプはありませんが、そのオブジェクトをプロトタイプとしたクラスがインスタンス化されると、そのオブジェクトのメンバーがそのクラスのインスタンスに渡されます。それがオブジェクトのプロトタイプになります。これがプロトタイプ継承の本質です。
プロトタイプ継承は、JavaScript の多くのネイティブ オブジェクトで使用される継承メソッドでもあります。
functionparentClass() {
/ / プライベート フィールド
var x = "私はparentClass フィールドです!";
// プライベート メソッド
function method1() {
alert(x); m aparentClass メソッド!");
}
// public field
this.x = "I'm aparentClass object field!";
// public メソッド
this.method1 = function( ) {
alert(x);
method1();
}
}
parentClass.prototype.method = function () {
alert("私はparentClassのプロトタイプメソッドです!");
}
parentClass.staticMethod = function () {
alert("私はparentClassの静的メソッドです!");
}
function subClass() {
// プライベート フィールド
var x = "私は subClass フィールドです!"
// プライベート メソッド
function method2() {
alert(x);
alert("私はサブクラスのメソッドです!");
}
// パブリックフィールド
this.x = "私はサブクラスのオブジェクトフィールドです!";
// パブリック メソッド
this.method2 = function() {
alert(x);
alert(this.x);
method2();
}
this.method3 = function() {
method1();
}
}
// 継承
subClass.prototype = newparentClass(); .constructor = subClass;
// テスト
var o = new subClass();
alert(oinstanceofparentClass); // true
>alert( o.constructor); // function subClass() {...}
o.method1(); // 私はparentClassフィールドです!
// 私はsubClassオブジェクトフィールドです!
// 私はparentClassフィールドです!
// 私はparentClassメソッドです!
o.method2(); // 私はsubClassフィールドです!
// 私はサブクラスのフィールドです!
o.method(); // 私はparentClassのプロトタイプメソッドです! >o.method3(); // エラー!!!
上記の例は、プロトタイプの継承の使用方法を示しています。 .継承します。