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 を実行する問題を解決する

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

推薦する

16 の XHTML1.0 と HTML の互換性ガイドラインの概要

1.ページを XML タイプとして宣言しないでください。ページでは UTF-8 または UTF-16...

HTML チュートリアル: よく使われる HTML タグのコレクション (5)

関連記事:初心者が学ぶ HTML タグ (4)導入された HTML タグは、必ずしも XHTML 仕...

HTML でフォントの色を設定する方法と、PS を使用して HTML で正確なフォントの色を取得する方法

1. HTMLフォントカラー設定HTML では、フォント タグを使用してフォント コンテンツの色を設...

Linux でローカル コンピューターとリモート サーバーのポートが接続されているかどうかを確認する方法

以下のように表示されます。 1. ssh -v -p [ポート番号] [ユーザー名]@[IPアドレス...

CSS ペイント API: CSS のような描画ボード

1. Canvas画像をCSS背景画像として使用するCSS ペイント API は、Canvas キャ...

タブバーのいくつかの実装方法(推奨)

タブ: カテゴリ + 説明タグバー: カテゴリ => ユーザーに現在地と目的地を知らせる1. ...

DOM操作テーブルの例(DOMはテーブルを作成します)

1. HTML タグを使用してテーブルを作成します。コードをコピーコードは次のとおりです。 <...

MySQLのイベントスケジューラEVENTを理解する

MySQL のイベント スケジューラ EVENT は、Unix crontab や Windows ...

HTML でテーブルを分割および結合する (colspan、rowspan)

このコードは水平マージを示しています。 <!DOCTYPE html PUBLIC "...

MySQL スロークエリログの役割と公開

序文MySQL スロー クエリ ログは、MySQL が提供するログ レコードの一種です。これは、応答...

Linuxでスワップパーティションファイルを作成する方法

スワップの紹介Linux のスワップ (スワップ パーティション) は、Windows の仮想メモリ...

::before/:before と ::after/:after の使用に関する深い理解

パート1: 基礎1. :active や :hover などの疑似クラスとは異なり、これらはすべて疑...

HTMLでvueとel​​ement-uiを直接参照する方法

コードは次のようになります。 <!DOCTYPE html> <html> ...

CentOS 7 ブートカーネルの切り替えとブートモードの切り替えの説明

Centos7 スイッチブートカーネル注: 必要に応じて、最初にyum update -yを実行して...

電子メールの HTML ページを作成するための原則の概要

HTML メールはこのサイト上の独立したホスト ページではないため、他の誰かによってホストされていま...