JS の 6 つの継承方法とその長所と短所

JS の 6 つの継承方法とその長所と短所

序文

継承は JS の 3 つの山の 1 つとして知られ、JS の世界に欠かせない要素です。この方法を使用すると、以前の開発コードをより適切に再利用し、開発サイクルを短縮し、開発効率を向上させることができます。

ES6 以前は、JS のクラスはコンストラクターによってシミュレートされており、実際のクラスはありませんでした。ES6 のクラスはシンタックスシュガーですが、この時期のクラスは関数として直接使用できます。ES6 以降、クラスは関数として使用できなくなりました。

継承について話す前に、クラスにはインスタンス属性とパブリック属性という 2 種類の属性があることを明確にする必要があります。以下で説明する継承方法はすべて、この 2 つの点を中心に展開されます。

関数 Animal(名前) {
  // インスタンスの属性 this.name = name;
}

// パブリックプロパティ Animal.prototype.eat = function() {
  // やること...
}

ES6 以前のコンストラクターを関数として直接呼び出すことを回避するにはどうすればよいですか?

ES5 ソリューション:

関数 Animal() {
    // new を使用せずに直接呼び出された場合は、例外をスローします if (!(this instanceof Animal)) {
        // new の原則。new を使用すると、これは Animal のインスタンスであり、この instanceof Animal は true になります。
        throw new Error("コンストラクターを直接呼び出さないでください");
    }
}

ES6 以降のソリューション:

関数 Animal() {
    // new が使用される場合、new.target はそれ自身を指します。それ以外の場合は未定義です。ただし、継承時には使用できません。インスタンスのプロパティを継承する場合、元の es5 では Animal.call(this) if (!new.target) { が使用されるためです。
        throw new Error("コンストラクターを直接呼び出さないでください");
    }
}

上記の両方の解決策は、関数として直接呼び出すことで解決できます。コンソールを直接呼び出すと、エラーが報告されます: キャッチされないエラー: コンストラクターを直接呼び出さないでください

次に、JSの継承メソッドを見てみましょう。

プロトタイプチェーン継承

プロトタイプ チェーン継承は、より一般的な継承方法の 1 つです。関係するコンストラクター、プロトタイプ、インスタンスの間には特定の関係があります。つまり、各コンストラクターにはプロトタイプ オブジェクトがあり、プロトタイプ オブジェクトにはコンストラクターへのポインターが含まれ、インスタンスにはプロトタイプ オブジェクトへのポインターが含まれます。

関数 Person(名前) {
    this.name = 名前;
    this.permission = ["ユーザー", "給与", "休暇"];
}

Person.prototype.say = 関数 () {
    console.log(`${this.name} スポーク`);
};

関数 スタッフ(年齢) {
    this.age = 年齢;
}

Staff.prototype = new Person("張三");

const zs = new Staff(12);
console.log(zs.name); // Zhang Sanzs.say(); // Zhang Sanが話しました

この時点で、コードは期待どおりになっています。次に、インスタンスを作成し、名前と権限を変更します。

const zs = new Staff(12);
const zs2 = 新しいスタッフ(18);
zs.permission.pop()
zs.name = '李斯';

コンソールログ(zs.name);
コンソールログ(zs2.name);
コンソールにログ出力します。
コンソールログ(zs2.permission);

最初の 2 つの出力は Li Si と Zhang San ですが、最後の 2 つの出力は両方とも ["user"、"salary"] で同じです。なぜこのようなことが起こるのでしょうか?
zs.name = '李四'; が実行されると、それは実際には代入演算です。代入後、zsは

zs2.nameはプロトタイプチェーンを検索し続けるので、最初の2つの出力はLi SiとZhang Sanです。

console.log(zs.__proto__ === zs2.__proto__); を通じて true を出力することで、2 つのインスタンスが同じプロトタイプ オブジェクト Person を使用し、メモリ空間を共有していることがわかります。一方が変更されると、もう一方もそれに応じて変更されます。

上記から、プロトタイプチェーン継承にはいくつかの欠点があることがわかります。

コンストラクタの継承

コンストラクタは通常、継承を完了するためにcallとapplyを使用します。

関数 Person(名前) {
    this.name = 名前;
    this.permission = ["ユーザー", "給与", "休暇"];
}

Person.prototype.say = 関数 () {
    console.log(`${this.name} スポーク`);
};

関数 スタッフ(名前, 年齢) {
    Person.call(これ、名前);
    this.age = 年齢;
}

Staff.prototype.eat = 関数(){
    console.log('食べようよ~~~');
}

const zs = new Staff("张三", 12);
コンソールログ(zs);

上記のコードのコンソール出力:

Staff のプロパティとメソッドを持っているだけでなく、インスタンス化されるたびに Person.call(this, name); が呼び出されるため、Person のプロパティも継承していることがわかります。これにより、プロトタイプ チェーン継承の問題を解決できます。

このとき、Personプロトタイプのメソッドを呼び出します。

zs.say()

このとき、コンソールに次のエラーが表示されます: Uncaught TypeError: zs.say is not a function

組み合わせ継承(プロトタイプチェーン継承とコンストラクタ継承の組み合わせ)

プロトタイプ チェーン継承とコンストラクター継承には、それぞれ独自の問題と利点があります。 2 つの継承方法を組み合わせると、複合継承が生成されます。

関数 Person(名前) {
    this.name = 名前;
    this.permission = ["ユーザー", "給与", "休暇"];
}

Person.prototype.say = 関数 () {
	console.log(`${this.name} スポーク`);
};

関数 スタッフ(名前, 年齢) {
    // Personの2回目の実行
    Person.call(これ、名前);
    this.age = 年齢;
}

Staff.prototype.eat = 関数(){
    console.log("食べようよ〜〜〜");
};

// Personの最初の実行
Staff.prototype = 新しい Person();
// StaffコンストラクタをStaffにポイントしない場合、Staffインスタンスzs.constructorはPersonにポイントします
Staff.prototype.constructor = スタッフ;

const zs = new Staff("张三", 12);
const ls = new Staff("Li Si", 12);
zs.permission.pop();
コンソールにログ出力します。
コンソールにログ出力します。
zs.say();
ls.say();

とりあえず、コンソール出力は正常で、上記の 2 つの継承の欠点は解決されましたが、次の 2 つの新たな問題が発生します。

  1. Person は 2 回実行されます: Person.call(this, name) と new Person()。1 回実行されることが想定されています。余分な実行により、パフォーマンスのオーバーヘッドが発生します。
  2. Staff.prototype = new Person() でいくつかのパブリック プロパティとメソッドが定義されている場合、それらは上書きされます。たとえば、インスタンスで zs.eat() を呼び出すことはできず、コンソールに Uncaught TypeError: zs.eat is not a function というエラーが報告されます。その後に定義すると、Person が汚染されます。

寄生遺伝

Object.create を使用して対象オブジェクトの浅いコピーを取得し、基本クラスの汚染を回避するためにいくつかのメソッドを追加することで、主に複合継承の 2 番目の問題を解決します。

主に次の2行のコードを置き換えます

Staff.prototype = 新しい Person();
Staff.prototype.constructor = スタッフ;

次と置き換えます:

Staff.prototype = Object.create(Person.prototype, {
    コンストラクタ: {
        // StaffコンストラクタをStaffにポイントしない場合、Staffインスタンスzs.constructorはPersonにポイントします
        値: スタッフ、
    },
});

組み合わせ寄生遺伝

今のところ、Person を 2 回インスタンス化するという問題は解決されていません。次の組み合わせた寄生継承は、上記の問題を完璧に解決できます。これは、ES6 以前のすべての継承方法の中でも最良の継承方法でもあります。

完全なコードは次のとおりです。

関数 Person(名前) {
    this.name = 名前;
    this.permission = ["ユーザー", "給与", "休暇"];
}

Person.prototype.say = 関数 () {
    console.log(`${this.name} スポーク`);
};

関数 スタッフ(名前, 年齢) {
    Person.call(これ、名前);
    this.age = 年齢;
}

Staff.prototype = Object.create(Person.prototype, {
    コンストラクタ: {
        // StaffコンストラクタをStaffにポイントしない場合、Staffインスタンスzs.constructorはPersonにポイントします
        値: スタッフ、
    },
});

Staff.prototype.eat = 関数(){
    console.log("食べようよ〜〜〜");
};

実際、継承する場合、Staff.prototypeを変更する方法は上記以外にもたくさんあり、他にもいくつかの方法があります。

  • prototype.__proto__ メソッド
Staff.prototype.__proto__ = Person.prototype

prototype.__proto__ には互換性の問題があります。それ自体では見つけることができません。プロトタイプ チェーンを上方向に検索し続けます。この時点で、Animal と Tiger は同じアドレスを共有しなくなり、お互いに影響を与えなくなります。

  • Object.setPrototypeOf メソッド
Object.setPrototypeOf(スタッフプロトタイプ、人物プロトタイプ)

es6構文は互換性があり、原則はprototype.__proto__メソッドです

拡張する

ES6 以降では継承に extends を使用できます。これは、現在の開発で最も一般的に使用されている方法でもあります。ブラウザのサポートは理想的ではありませんが、今日のエンジニアリングの向上により、これらは使用を制限する理由ではなくなりました。

クラス Person {
    コンストラクタ(名前) {
        this.name = 名前;
        this.permission = ["ユーザー", "給与", "休暇"];
    }

    言う() {
        console.log(`${this.name} スポーク`);
    }
}

クラスStaffはPersonを拡張します{
    コンストラクタ(名前, 年齢) {
        super(名前);
        this.age = 年齢;
    }

    食べる() {
        console.log("食べようよ〜〜〜");
    }
}

実際、ES6 継承は babel によってコンパイルされた後、複合寄生継承も採用されるため、その継承原則を習得することに重点を置く必要があります。

要約する

以上で、JS における 6 つの継承方法とそのメリット・デメリットについての説明は終了です。JS の継承方法とそのメリット・デメリットについてさらに詳しく知りたい方は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続きご覧ください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • 1 つの記事で JavaScript の継承を理解する
  • js の一般的な継承方法 6 つの概要
  • JS上級編ES6の6つの継承方法
  • ネイティブ JavaScript 継承方法とその長所と短所の詳細な説明
  • jsの継承の6つの方法を詳しく解説
  • JavaScriptで継承を実装するいくつかの方法
  • 6 つの JavaScript 継承方法とその長所と短所 (まとめ)
  • JS で継承を実装する一般的な方法の例
  • JavaScriptで複数の継承メソッドを共有する

<<:  docker インストール後に hello-world を実行する問題を解決する

>>:  テーブルタグ(テーブル)詳細

推薦する

Linux 名前空間ユーザーの詳細な説明

ユーザー名前空間は Linux 3.8 で追加された新しい名前空間で、ユーザー ID やグループ I...

Zabbixのカスタム監視項目とトリガーについて

目次1. 監視ポート関係の説明操作する2. 監視サービス関係の説明操作する3. テンプレートのインポ...

Linux 構成 SSH パスワードフリーログイン「ssh-keygen」の基本的な使い方

目次1 SSHとは何か2 SSHパスワードフリーログインを設定する2.1 必要なソフトウェアのインス...

メタタグのビューポートはデバイス画面のCSSを制御します

コードをコピーコードは次のとおりです。 <meta name="viewport&q...

XHTML におけるタイトルタグと段落タグの使用に関する詳細な説明

XHTML 見出しの概要Word 文書を作成するときは、「第 1 章」、「1.2.1」などのタイトル...

divコンテナ内の背景色または画像は、サイズが大きくなるにつれて大きくなります。

コードをコピーコードは次のとおりです。高さ:自動 !重要;高さ:550px;最小高さ:550px; ...

複数レベルの複雑な動的ヘッダーの avue-crud 実装例

目次序文バックグラウンドデータの結合フロントエンドデータ表示ページ効果表示Avue.js は、既存の...

Linux ファイルを分割するための split コマンドの詳細な説明

いくつかの簡単な Linux コマンドを使用すると、ストレージまたは電子メールの添付ファイルのサイズ...

CSS3 @mediaの基本的な使い方のまとめ

//文法: @media mediatype and | not | only (メディア機能) ...

Linux でスワップ領域を確認する 5 つのコマンドの概要

序文Linux では、スワップ パーティションとスワップ ファイルの 2 種類のスワップ領域を作成で...

CSS スキル コレクション - 古典の中の古典

リンク上の点線のボックスを削除しますコードをコピーコードは次のとおりです。 a:アクティブ、a:フォ...

Docker で Tomcat、MySQL、Redis をインストールするための詳細な手順

目次DockerでTomcatをインストールするtomcatイメージを使用してコンテナを作成する(イ...

Vue プロジェクト @change 複数のパラメータを使用して複数のイベントを渡す

まず、変更イベントは 1 つだけです。 changelevel() //値を選択選択を変更して行の値...

リクエスト IP の最後のセグメントに基づいてトラフィックを分割するように Nginx を構成する方法

これは主に、場所パラメータのif判断の設定ジャンプです。迂回により、サーバーの負荷と圧力を軽減できま...

JavaScript における async と await の使い方とメソッド

JS の async 関数と await キーワード 関数ヘルワールド() { 「こんにちは!美しい...