😍プロトタイプチェーンの紹介#
プロトタイプオブジェクト#
簡単な始まりを話しましょう
JS では
すべてのものはオブジェクトに基づいています
- 関数の
prototype
はプロトタイプオブジェクトを指します- プロトタイプオブジェクトの
constructor
は関数を指します- プロトタイプオブジェクト上に定義された属性とメソッドは、すべての関連するインスタンスオブジェクトによって共有され、継承されます
prototype
は関数独自のものです__proto__
属性は、オブジェクト独自のものです
- JS で関数を作成すると、必ず
prototype
属性が生成され、この属性はオブジェクトを指します。このオブジェクトがその関数のプロトタイプオブジェクトです。
このオブジェクトにはconstructor
属性が含まれており、この属性はその関数を指します。
2. この関数のインスタンスオブジェクトを宣言すると、そのオブジェクトは__proto__
属性を持ち、この属性はそのインスタンスオブジェクト(ha)
のプロトタイプオブジェクト({constructor:f})
を指します。したがって、関数のprototype
とインスタンスオブジェクトの__proto__
を等価に比較することができ、その結果はもちろんtrue
です。
// Haという名前の関数を作成
function Ha(){
this.name='ハラ'
}
// Haのprototypeを呼び出す
Ha.prototype //{constructor:f}
// インスタンスオブジェクトを宣言
let ha = new Ha()
// プロトタイプオブジェクトの比較
ha.__proto__ === Ha.prototype // true
// コンストラクタはha.constructorを通じて直接呼び出せる
ha.__proto__.constructor === ha.constructor
プロトタイプチェーン#
プロトタイプチェーンはとても簡単です。以下のコードを見ればわかります。
ha.__proto__.__proto__.__proto__ // null
// なぜnullになるのかを説明します
ha.__proto__はHaのプロトタイプオブジェクトを指します
ha.__proto__.__proto__はHaのプロトタイプオブジェクトObjectのプロトタイプオブジェクトを指します
ha.__proto__.__proto__.__proto__は、すべてのものがオブジェクトに基づいているので、Objectの上層のプロトタイプオブジェクトはまだ存在しますか?もちろん存在しないので、nullを返します。
// コンストラクタの継承
function Haha(name){
this.name=name
Ha.call(this)
}
// まずHahaクラスのインスタンスを宣言し、インスタンスのname属性を呼び出すと、印刷される値はHaのname属性の値です。継承を使用しているため、現在のインスタンスのコンストラクタ内に存在しない属性は、Haのコンストラクタのname属性を上層に探しに行きます。存在すれば印刷され、存在しなければObjectのコンストラクタのname属性を探し続け、依然として存在しなければnullになります。
let haha = new Haha()
haha.name // ハラ
// まずHahaクラスのインスタンスを宣言し、引数を渡すと、インスタンスのname属性を呼び出すと、印刷される値は王老五になります。
let haha = new Haha('王老五')
haha.name // 王老五
コンストラクタ、インスタンス、プロトタイプの関係#
コンストラクタはインスタンスを構築するための関数です。各インスタンスにはプロトタイプがあり、プロトタイプはコンストラクタのプロトタイプを指します。
// haのプロトタイプはHa.prototype
// Ha.prototypeのプロトタイプはObject.prototype
function Ha(){} // コンストラクタ
let ha=new Ha() // インスタンス
Ha.prototype.constructor === Ha //true コンストラクタプロトタイプのconstructorはコンストラクタ自体を指します
継承#
コンストラクタの継承#
call () を使用して Parent の this を Child のインスタンスに指向させ、継承を実現します。
利点
- インスタンスを作成する際に親クラスのコンストラクタに引数を渡すことができます。
- 各子クラスのインスタンスにはそれぞれの親クラスのインスタンスがあり、親クラスのインスタンス属性を変更しても、同じ親クラスを継承する他の子クラスには影響しません。
欠点
- 親クラスのコンストラクタ内の属性とメソッドのみを継承し、プロトタイプオブジェクト上の内容を継承できません。
- 再利用できません。
function Parent(){
this.name='father'
}
Parent.prototype.sayHello = function () {
return this.name
}
function Children(){
Parent.call(this)
}
const c = new Children()
c.name // father
c.sayHello // c.sayHello is not a function
プロトタイプチェーンの継承#
プロトタイプオブジェクトを再定義します。
利点
- 関数の再利用、子クラスは親クラスの属性とメソッドを使用できます。
- 子クラスは親クラスのプロトタイプオブジェクト上の属性メソッドに直接アクセスできます。
欠点
- 親クラスのコンストラクタに引数を渡すことができません。
- 親クラスの参照属性はすべての子クラスに共有されます。ある子クラスが参照属性を変更すると、他の子クラスにも影響が及びます。なぜなら、同じメモリアドレスを操作しているからです。
- 親クラスのプライベート変数は子クラスで露出します。
function Parent(){
this.name='father'
this.hobby = ['歌', 'ダンス']
}
Parent.prototype.sayHello = function(){
return this.name
}
function Children(){}
Children.prototype = new Parent()
const c = new Children()
const c2 = new Children()
c.name // father
c.sayHello() // father
c.hobby.push('ラップ')
c.hobby // ['歌', 'ダンス', 'ラップ']
c2.hobby // ['歌', 'ダンス', 'ラップ']
组合继承#
コンストラクタ + プロトタイプチェーン
利点
- 親クラスのインスタンス属性とプロトタイプオブジェクトのすべての属性メソッド(プライベートを除く)を継承します。
- 参照タイプの属性がすべてのインスタンスで共有される問題を回避します(プロトタイプチェーンの継承)。
欠点
- 親クラスのインスタンスを 2 回呼び出すため、パフォーマンスに影響があります。
function Parent(){
this.name='father'
}
Parent.prototype.sayHello = function(){
return this.name
}
function Children(){
Parent.call(this)
}
Children.prototype = new Parent()
Children.prototype.constructor = Children
const c = new Children()
c.name // father
c.sayHello() // father
寄生式継承#
プロトタイプ式継承 + オブジェクトの強化
利点
- 親クラスのインスタンス属性とプロトタイプオブジェクトのすべての属性メソッド(プライベートを除く)を継承します。
- 参照タイプの属性がすべてのインスタンスで共有される問題を回避します(プロトタイプチェーンの継承)。
欠点
- 親クラスのインスタンスを 2 回呼び出すため、パフォーマンスに影響があります。
function Parent(){
this.name='father'
}
Parent.prototype.sayHello = function(){
return this.name
}
function Children(){
Parent.call(this)
}
Children.prototype = new Parent()
Children.prototype.constructor = Children
const c = new Children()
c.name // father
c.sayHello() // father
寄生式継承#
子クラスのコンストラクタ内で、親クラスのインスタンスの機能を強化します。
利点
- 参照タイプの属性がすべてのインスタンスで共有される問題を回避します(プロトタイプチェーンの継承)。
欠点
- 関数の再利用を実現できず、各子クラス内部で親クラスをインスタンス化しています。
- 親クラスのプライベート変数にアクセスできるのは、実際には子クラスに親クラスのインスタンスが埋め込まれているからです。
function Parent() {
this.name = 'father'
this.hobby = ['歌', 'ダンス']
}
Parent.prototype.sayHello = function () {
return this.name
}
function Children() {
let parent = new Parent()
parent.sayBye = function Bye() {
return this
}
return parent
}
const c = new Children()
console.log(c.sayHello());
寄生合成継承#
子クラスのコンストラクタ内で、親クラスのインスタンスの機能を強化します。
利点
- 参照タイプの属性がすべてのインスタンスで共有される問題を回避します(プロトタイプチェーンの継承)。
- Parent コンストラクタを 1 回だけ呼び出します。
欠点
- 関数の再利用を実現できず、各子クラス内部で親クラスをインスタンス化しています。
- 親クラスのプライベート変数にアクセスできるのは、実際には子クラスに親クラスのインスタンスが埋め込まれているからです。
function _extends(children, parent) {
let parent_prototype = Object.create(parent.prototype)
parent_prototype.constructor = children
children.prototype = parent_prototype
}
function Parent() {
this.name = 'father'
this.hobby = ['歌', 'ダンス']
}
Parent.prototype.sayHello = function () {
return this.name
}
function Children() {
Parent.call(this)
}
_extends(Children, Parent)
const c = new Children()
console.log(c.sayHello()) // father