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

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

推薦する

Docker で Java 8 Spring Boot アプリケーションを開発する方法

この記事では、ローカル マシンに Java 8 をインストールせずに、Java 8 を使用して簡単な...

IE8を閲覧するときにウェブサイトが自動的にIE7互換モードを使用するようにする

序文IE の将来のすべてのバージョンで Web ページの外観が一貫していることを保証するために、IE...

CSS マージンの重複と解決策の探索の詳細な説明

最近、CSS 関連の知識ポイントをいくつか見直し、CSS における典型的なマージンの重なりの問題を整...

React における useEffect と useLayoutEffect の違い

目次前提条件使用効果コミット前ミューテーション効果コミットミューテーション効果コミットレイアウト効果...

Linuxファイルシステム操作の実装

この読書ノートでは、主にファイルシステムに関連する操作を記録します。ディスクとディレクトリの容量ディ...

HTML DOM入門_PowerNode Javaアカデミー

DOMとは何ですか? JavaScript を使用すると、HTML ドキュメント全体を再構築できます...

アプレットにおけるwx.getUserProfileインターフェースの具体的な使用

最近、WeChatミニプログラムは、監査ミニプログラムのwx.loginおよびwx.getUserI...

MYSQL は、指定されたユーザーのランキングとクエリを実装します。ランキング関数 (並列ランキング関数) のサンプルコード

序文この記事は主に、MYSQL でランキングを実現し、指定ユーザーランキング関数 (並列ランキング関...

vue シンプルメモ帳開発の詳しい説明

この記事では、参考までにEasy Notepadを実装するためのVueの具体的なコードを紹介します。...

vue3 における vuex と pinia の落とし穴

目次導入インストールと使用方法文章の相違点と類似点の簡単な比較VuexとPiniaの長所と短所Pin...

JS を使用して HTML で回転するクリスマスツリーを実装する

<!DOCTYPE ヘムル パブリック> <html> <ヘッド&g...

Linux に mysql をインストールするときに /etc に my.cnf ファイルがない問題を解決する

今日、mysql ポートを変更したいと思ったのですが、/etc/ ディレクトリに my.cnf ファ...

CSSスタイルの記述順序と命名規則と注意事項

書き順の重要性ブラウザのリフローを減らし、ブラウザのDOMレンダリングパフォーマンスを向上させる①:...

HTML のボタン タグをクリックしてページにジャンプする 3 つの方法

方法1: onclickイベントを使用する <input type="button&...

Linux クラウド サーバーに JDK と Tomcat をインストールするための詳細な手順 (推奨)

JDKをダウンロードしてインストールするステップ 1: まず、公式 Web サイト http://...