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

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

推薦する

Windows で mysql 8.0.12 をインストールするための詳細なチュートリアル

この記事では、MySQL 8.0.12のインストール方法に関する詳細なチュートリアルを参考までに紹介...

きれいなJavaScriptコードの書き方を教える記事

目次1. 変数意味のある名前を使う不必要なコンテキストを追加しないようにするハードコードされた値を避...

MySQL での and or クエリの優先度分析

これは見落とされがちな問題かもしれません。まず、次の点を明確にする必要があります。 MySQL では...

Centos7 に MySQL 8.0.23 をインストールする手順 (初心者レベル)

まず、MySQL とは何かを簡単に紹介します。簡単に言えば、データベースはデータを格納するための倉庫...

Javascript 操作メカニズム イベントループ

目次1. 4つのコンセプト1. JavaScriptはシングルスレッドです2. タスクキュー3. 同...

CSS を使用して複数の方法で等高レイアウトを実装するサンプル コード

この記事で説明する等高レイアウトでは、純粋な CSS を使用して、要素の高さを手動で設定することなく...

プロジェクトにaxiosをカプセル化する実際のプロセス

目次序文axiosカプセル化の利点パッケージのアイデア設定の優先順位axiosインスタンス構成1. ...

Vueのライブ放送機能の詳しい説明

最近、会社でたまたま生放送をしていたのですが、今日は私が遭遇した落とし穴を記録します。会社のサーバー...

LeetCode の SQL 実装 (183. 注文をしたことがない顧客)

[LeetCode] 183.注文しない顧客Web サイトに、Customers テーブルと Or...

CentOS7 に ElasticSearch 6.4.1 をインストールするための詳細なチュートリアル

1. ElasticSearch 6.4.1 インストール パッケージを次の場所からダウンロードしま...

Reactベースのコンポーネントのカプセル化の実装手順

目次序文antd はどのようにしてコンポーネントをカプセル化するのでしょうか?ディバイダーコンポーネ...

HTML+SassはHambergurMenu(ハンバーガーメニュー)を実装します

先日、外国人の方がHTML+CSSを使ってHamburgerMenuを実装している動画を見ました。最...

Vue プロジェクトに Electron を追加するための詳細なコード

1. package.jsonに追加する "メイン": "electr...

Linux システムをバックアップする docker コマンドの詳細な説明

tar バックアップ システム sudo tar cvpzf backup.tgz --exclud...

上部の固定ナビゲーションバーによって CSS アンカーの配置がブロックされる問題の解決方法

多くのウェブサイトでは、ユーザーが簡単に検索したり他のページに移動したりできるように、上部にナビゲー...