JavaScriptにおける継承方式

9734 ワード

原型チェーンを借りる
ECMAScriptではプロトタイプチェーンの概念を述べ、プロトタイプチェーンを継承を実現する主要な方法として用いた.その主な思想は構造関数、プロトタイプと実例の関係を利用することです.を選択します.各関数にはprototype属性があり、この属性は関数のプロトタイプオブジェクトを指すことを知っています.この関数がコンストラクタとして実装されると、そのインスタンスの内部に[[Prototype]]属性があり、この属性はこのコンストラクタのプロトタイプオブジェクトを指す.一つのコンストラクタのプロトタイプが他の例に等しい場合、このコンストラクタのプロトタイプは他のプロトタイプオブジェクトを指すポインタを含んでいます.このようなプロトタイプと実例との関係は、プロトタイプチェーンを借りて継承を実現するための基本的な考え方である.
例を挙げて説明します
function SuperType() {
  this.superType = 'SuperType';
}
SuperType.prototype.getSuper = function() {
  return this.superType;
}

function ChildType() {
  this.childType = ';childType'
}
//    SuperType
ChildType.prototype = new SuperType(); 

let instance1 = new ChildType();

console.log(instance1.getSuper()); // 'SuperType'

上記の例では、2つの関数SuperTypeChildTypeが定義されていますが、それらの主な違いはChildTypeSuperTypeを継承しています.この継承の実現は、SuperTypeのインスタンスを作成し、このインスタンスをChildType.prototypeに割り当てることによって実現される.
プロトタイプチェーンによって継承される問題のプロトタイプチェーンは非常に強力であり、それを用いて継承が可能であるが、プロトタイプによって継承が実現されると、プロトタイプは実際に別のコンストラクションの実例となり、元のインスタンス属性はプロトタイプの属性となり、この属性は共有属性と呼ばれるようになる.この例は次のようによく説明されます.
function SuperType() {
  this.colors = ['red', 'green', 'blue']
}
function ChildType() {
}
ChildType.prototype = new SuperType();

let instance1 = new ChildType();

instance1.colors.push('yellow');
console.log(instance1.colors); // ["red", "green", "blue", "yellow"]

let instance2 = new ChildType();
console.log(instance2.colors); // ["red", "green", "blue", "yellow"]
この例ではSuperTypeにおいて、colors属性が定義されています.SuperTypeの各例には、このColors属性が含まれています.ChildType.prototypeSuperTypeの例になると、ChildType.prototypeにもこのcolors属性が含まれています.エンジンはプロトタイプチェーンに沿ってinstance1.colorsChildType.prototype属性を見つけることができますので、colors属性の動作はcolorsの他の例にも影響します.そのため、プロトタイプチェーンを単独で使って継承することはあまりない.
構造関数を借りる
構造関数の借用は、ときには偽造オブジェクトまたは古典的継承とも呼ばれ、その基本的な考え方は、サブタイプのコンストラクタにおいてChildTypeまたはapply()を使用して、超タイプ(親タイプ)コンストラクタを呼び出すことである.例を挙げて説明します
function SuperType() {
  this.colors = ['red', 'green', 'blue']
}

function ChildType() {
  SuperType.call(this);
}

let instance1 = new ChildType();

instance1.colors.push('yellow');
console.log(instance1.colors); // ["red", "green", "blue", "yellow"]

let instance2 = new ChildType();
console.log(instance2.colors); // ["red", "green", "blue"]
call()方法を使用することによって、私たちは実際にはcall()例を新たに作成した環境でChildTypeを呼び出し、このようにして、SuperTypeの新しい例でChildTypeの初期化コードを実行することになります.プロトタイプ鎖の借用の大きな利点として,構造関数により,サブタイプコンストラクタが超型コンストラクタにパラメータを伝達することができるということである.この例を見てください.
function SuperType(name) {
  this.name = name;
}

function ChildType(name) {
  SuperType.call(this, name);
}

let instance1 = new ChildType('Nick');

console.log(instance1.name); // 'Nick'
コンストラクタを借りる問題がコンストラクタだけを借りると、方法はすべてコンストラクタの中でしか定義できないので、関数の多重性はもう存在しなくなり、またサブタイプのコンストラクタにとっては超タイプのプロトタイプ上に定義する方法はサブタイプの構造関数には見えない.結果として、すべてのタイプはコンストラクターモードのみを使用できます.従って,構造関数を借りるこの方法は単独で使うことも少ない.
グループ引継ぎ
コンボ継承は、プロトタイプチェーンとコンストラクターの2つの方法を組み合わせることで、両者の長い継承方式を発揮する場合もあります.基本的な考え方は、プロトタイプチェーンを利用してプロトタイプの属性と方法の継承を実現し、構成関数によってインスタンス属性の継承を実現することである(各インスタンスには個別の例示的な属性があることを保証し、相互に影響を与えない).
function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'green', 'yellow'];
}
SuperType.prototype.getColors = function() {
  return this.colors;
}

function ChildType(name) {
  //     
  SuperType.call(this, name);
}
//     
ChildType.prototype = new SuperType();

let instance1 = new ChildType('Nick');
let instance2 = new ChildType('Cherry');
instance1.colors.push('black');

console.log(instance1.name); // 'Nick'
console.log(instance1.colors); // ' ["red", "green", "yellow", "black"]'
instance1.getColors(); // ' ["red", "green", "yellow", "black"]'
console.log(instance2.name); // 'Cherry'
console.log(instance2.colors); //  ["red", "green", "yellow"]
プロトタイプチェーンとコンストラクターを単独で使用して継承を実現する際の欠点は、JavaScriptで最も一般的な継承方式である.
プロトタイプ継承
プロトタイプ継承は2006年にダグラス・クロークフォードが提案したもので、彼の基本思想はプロトタイプを利用して既存のオブジェクトに基づいて新しいオブジェクトを作成すると同時に、カスタムタイプを作成する必要がありません.彼は次のような関数を与えた.
function object (o) {
  function F() {};
  F.prototype = o;
  return new F();
}
let person = {
  name: 'Nick',
  friends: ['cherry', 'july'],
};
let anotherPerson = object(person);
anotherPerson.name = 'lily';
anotherPerson.friends.push('Tom');

let person2 = object(person);
person2.name = 'Jone';
person2.friends.push('Linda'); 

console.log(person.friends); // ["cherry", "july", "Tom", "Linda"]
プロトタイプの継承要求は、オブジェクトを別のオブジェクトの基礎として必要です.この例では、personオブジェクトをベースに、personオブジェクトをSuperType関数に導入し、他の新しいオブジェクトに戻ります.この新しいオブジェクトは、personをプロトタイプとしていますので、プロトタイプにはname属性とfriends属性が含まれています.これは、person.friendsがpersonだけでなく、another Personとperson 2によって共有されていることを意味します.
ES 5は、新たにcolors方法でプロトタイプ継承を規範化した.この方法は2つのパラメータを受け入れる.1つは新しいオブジェクトのプロトタイプとしてのオブジェクトと新しいオブジェクトのために追加の属性を定義するオブジェクトである.一つのパラメータが入ってきた場合、Object.creat()は上のobject()と同じです.
let person = {
  name: 'Nick',
  friends: ['cherry', 'july'],
};

let person1 = Object.create(person);
person1.name = 'Jhon';
person1.friends.push('cherry');

let person2 = Object.create(person);
person2.name = 'Lily';
person2.friends.push('Bob');

console.log(person.friends); // ["cherry", "july", "cherry", "Bob"]
object()方法の第2のパラメータは、Object.create()方法を使用する第2のパラメータと同じである.各属性は、属性記述子によって追加される.
let person = {
  name: 'Nick',
  friends: ['cherry', 'july'],
};

let anotherPerson = Object.create(person, {
  name: {
    value: 'Lily',
  }
})
console.log(anotherPerson.name); // Lily
console.log(person.name); // Nick
大勢を動員する必要がない構造関数を作成します.一つのオブジェクトともう一つのオブジェクトに上のような関係を維持したいだけです.原型式継承は完全に適任です.ただし、参照タイプを含む属性は常に共有されており、プロトタイプチェーンを使用して継承されているようです.
寄生式引継ぎ
寄生式継承はプロトタイプの継承と密接につながっている思想で、その基本的な考え方は:パッケージ相続プロセスの関数だけを作成し、関数の内部にある方法で対象を強化し、最後に本当にそれがすべての仕事をしたように対象に戻ります.例:
function object(o) {`
  function F() {};
  F.prototype = o;
  return new F();
}

function createObject(original) {
  let clone = object(original);
  clone.sayHi = function() {
    console.log('hi');
  };
  return clone;
}

let person = {
  name: 'Nick',
  friends: ['Tom', 'Jhon'],
}
let person1 = createObject(person);
person1.sayHi(); // 'hi'
カスタムタイプやコンストラクタではなくオブジェクトを主に考慮した場合には,寄生型継承も有用なモードである.寄生継承を用いてオブジェクトに関数を追加すると、関数が多重化されないため効率が低下します.
寄生ユニット引き継ぎ
前に述べましたが、組み合わせの継承はJavaScriptの中の古典的な継承方式です.組み合わせ継承の最大の問題は、どのような場合でも二次構造関数を呼び出します.一度はサブタイプのプロトタイプを作成する時、一回はサブタイプのコンストラクタの内部にあります.この例を見てください.
function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'yellow', 'blue'];
}
SuperType.prototype.sayName = function() {
  console.lofg(this.name)
}
function ChildType(name, age) {
  SuperType.call(this, name); //      SuperType()
  this.age = age;
}
ChildType.prototype = new SuperType(); //      SuperType()
ChildType.prototype.constructor = ChildType;
ChildType.prototype.sayAge = function() {
  console.log(this.age);
}
Object.create()コンストラクタが初めて呼び出されたとき、Object.defineProperties()は2つの属性を得る.nameとcolorsはいずれもSuperTypeの例示的な属性である.ただし、ChildTypeのプロトタイプにおいては、SuperTypeを呼び出して実装すると、再度SuperType関数を呼び出すことができ、今度は新しいオブジェクトにChildType.prototypeChildType属性を作成しました.この二つの属性はChildTypeの原型の中の二つの同名属性を遮断します.したがって、2組のnameとcolors属性が生じる.1組はChildTypeの原型の中で、1組はChildTypeの例にある.これは二次コンストラクタを呼び出した結果です.この問題を解決する方法は、寄生式の組み合わせを継承することです.
寄生式グループ継承の基本パターンは以下の通りです.
function object(o) {
  function F() {};
  F.prototype = o;
  return new F();
}
function inheritPrototype(childType, superType) {
  let prototype = object(superType.prototype);   //     
  prototype.constructor = childType;            //     
  childType.prototype = prototype;             //     
}
例のnameは、寄生結合式継承の最も簡単な形態を完成させる.二つのパラメータを受け入れます.サブタイプのコンストラクタと超タイプのコンストラクタです.関数内部では、第1ステップは超タイププロトタイプのコピーを作成し、第2ステップは作成したコピーにcolors属性を追加し、プロトタイプを書き換えた後のconstrutor属性の損失を補う.最後のステップは、新しく作成したプロトタイプのコピーをサブタイプのプロトタイプに割り当てます.このように、前の例のサブタイププロトタイプのためのプロトタイプの割り当てを置き換えるためにinheritPrototypeを呼び出すことができる.
function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'yellow', 'blue'];
}
SuperType.prototype.sayName = function() {
  console.log(this.name)
}
function ChildType(name, age) {
  SuperType.call(this, name);
  this.age = age;
}

inheritPrototype(ChildType, SuperType);

ChildType.prototype.sayAge = function() {
  console.log(this.age);
}
let instance1 = new ChildType('cherry', 20);
instance1.sayName(); // 'cherry'
instance1.sayAge(); // 20
この例は、一度だけ呼び出されたconstructorを効率的に実現し、したがって、inheritPrototypeにおいて不必要な属性を作成することを回避する.同時に、プロトタイプチェーンは不変のままであり、SuperType( fおよびChildType 142を正常に使用することができる.寄生結合式継承は理想的な継承方式と考えられています.