JS継承の実現


JSは誕生当初から対象に向けた言語ではなかった.
どのようにJSで継承を実現するかについては、まとめて4つの書き方があります.
コンストラクタ継承
function Animal(name) {
    this.name = name
        
    this.sayName = function() {
        console.log(this.name)
    }
}
    
function Dog(name, hobby) {
    //   
    let ani = new Animal(name)
    for(let p in ani) {
        if (ani.hasOwnProperty(p)) {
            this[p] = ani[p]
        }
    }
        
    this.hobby = hobby
}
    
let dog1 = new Dog('xiaohei', 'bone')
let dog2 = new Dog('fofo', 'bone and fish')
console.log(dog1.sayName()) // xiaohei
console.log(dog2.sayName()) // fofo
対象と偽って相続を実現し、実際には構造関数において、親類のすべての属性を取得し、自身のオブジェクトに保存することにより、親類の属性と方法を呼び出すことができる.ここでforinの方式は親の属性を遍歴し、forinは公開された属性と方法を遍歴するので、hasOwnProperty現在のオブジェクトに書き込む範囲を制御する.そうでないと、すべての属性はすべてプライベート属性に変わります.
このようにして一つの欠点があります.親タイプの公開方法と属性にアクセスできないということです.
Animal.prototype.sayHobby = function() {
    console.log(this.hobby)
}
dog1.sayHobby() // VM2748:1 Uncaught TypeError: dog1.sayHobby is not a function at :1:6
コードの最適化
サブクラスでは、親タイプのプライベート属性を取得する必要がある場合には、callおよびapply親タイプの方法を呼び出したときに、現在のコンテキストをサブクラスのオブジェクトに変更すると、サブクラスのオブジェクトは、親タイプのすべてのプライベート属性を取得することができる.
function Animal(name) {
    this.name = name
        
    this.sayName = function() {
        console.log(this.name)
    }
}
    
function Dog(name, hobby) {
    //           
    Animal.call(this, name)
    
    this.hobby = hobby
}
    
let dog1 = new Dog('xiaohei', 'bone')
let dog2 = new Dog('fofo', 'bone and fish')
console.log(dog1.sayName()) // xiaohei
console.log(dog2.sayName()) // fofo
クラス継承
function Animal(name) {
    this.name = name || 'animal'
    this.types = ['cat', 'dog']
    
    this.sayTypes = function() {
        console.log(this.types.join('-'))
    }
}
Animal.prototype.sayName = function() {
    console.log(this.name)
}

function Dog(name) {
    this.name = name    
}
Dog.prototype = new Animal('animal')

let dog1 = new Dog('xiaohei')
dog1.sayName() // xiaohei

let dog2 = new Dog('feifei')
dog2.sayName() // feifei
この継承方式は、子類prototype.__proto__親類prototypeを引用することにより、親類のプライベート方法と共有方法にサブクラスをアクセスさせることができる.詳細はキーワードnewの実現を見ることができます.
クラス継承には二つの欠点があります.
  • トラップ・サブオブジェクトを参照して、親の種類の方法や変数を任意に修正し、他のサブオブジェクトのdog 1.types.push('fish')consosolie.log(dog 1.types)///[cat],[dog],[fish]consolip.log(dog 2.types)/[fish]
  • 構成の異なる実例となる属性を初期化できない
  • これは主にクラス継承のためです.Dog.prototype = new Animal('animal')によって実現されます.私たちは父の構造関数を一度だけ呼び出します.したがって、サブクラスでは、上記name属性のみを書くことができます.サブクラスでは、書き換えが必要です.
    グループ引継ぎ
    組み合わせ継承とは、以上の2つの継承方式の利点を組み合わせて、両者の欠点を捨てて、実現される組み合わせのことです.
    function Animal(name) {
        this.name = name
        this.types = ['dog', 'cat']
    }
    Animal.prototype.sayName = function() {
        console.log(this.name)
    }
    
    function Dog(name, hobby) {
        //                 ,          ,            
        Animal.call(this, name)
        this.hobby = hobby
    }
    //           prototype      
    Dog.prototype = new Animal()
    Dog.prototype.constructor = Dog
    Dog.prototype.sayHobby = function() {
        console.log(this.hobby)
    }
    
    // test instance of dog1
    let dog1 = new Dog('xiaohei', 'bone')
    dog1.sayName() // xiaohei
    dog1.sayHobby() // bone
    dog1.types.push('ant') // types: ['dog', 'cat', 'ant']
    
    // test instance of dog2
    let dog2 = new Dog('feifei', 'fish')
    dog2.sayName() // feifei
    dog2.sayHobby() // fish
    dog2.types // ['dog', 'cat']
    
    組み合わせモードは、構造関数の継承とクラスの継承による問題を解決し、理想的な継承方式として計算されますが、ここにはいくつかの欠点があります.
    この問題を解決するために最適化し、このような継承方式が生まれた.
    コンビネーション寄生式継承
    function Animal(name) {
        this.name = name
        this.types = ['dog', 'cat']
    }
    Animal.prototype.sayName = function() {
        console.log(this.name)
    }
    
    function Dog(name, hobby) {
        //                 ,          ,            
        Animal.call(this, name)
        this.hobby = hobby
    }
    
    /**         **/
    
    Dog.prototype = Object.create(Animal.prototype)
    //    Animal.prototype      ,    Dog      ,        Dog     
    Dog.prototype.constructor = Dog
    Dog.prototype.sayHobby = function() {
        console.log(this.hobby)
    }
    
    // test instance of dog1
    let dog1 = new Dog('xiaohei', 'bone')
    dog1.sayName() // xiaohei
    dog1.sayHobby() // bone
    dog1.types.push('ant') // types: ['dog', 'cat', 'ant']
    
    // test instance of dog2
    let dog2 = new Dog('feifei', 'fish')
    dog2.sayName() // feifei
    dog2.sayHobby() // fish
    dog2.types // ['dog', 'cat']
    
    MDN解釈:Object.create()方法は新しいオブジェクトを作成し、既存のオブジェクトを使用して新規作成の対象を提供する.proto_
    Object.createを使って、浅いコピーをして、親の原型の方法をコピーしてDocg.prototypeに与えます.このような種類では、父の種類の共有方法があります.また、父のタイプを呼び出すためのコンストラクターが少なくなります.
    書き換えcreate方法:
    function create(target) {
        function F() {}
        F.prototype = target
        return new F()
    }
    
    また、サブクラスのconstructorは、サブクラスのprototypeを変更するので、サブクラスの構造関数を再設定する必要があります.
    ES 6では文法飴を使ってextendsを実現します.
    前に勉強したことがある、あるいは対象言語の基礎に向かっているものがあると、これは分かりやすく、extensのキーワードを継承として使っています.
    class Animal {
        constructor(name) {
            this.name = name
        }
        
        sayName() {
            console.log(this.name)
        }
    }
    
    class Dog extends Animal {
        constructor(name, hobby) {
            super(name)
            this.hobby = hobby
        }
        
        sayHobby() {
            console.log(this.hobby)
        }
    }
    
    let dog1 = new Dog('xiaohei', 'bone')
    dog1.sayName() // xiaohei
    dog1.sayHobby() // bone
    
    let dog2 = new Dog('feifei', 'fish')
    dog2.sayName() // feifei
    dog2.sayHobby() // fish
    
    締め括りをつける
    以上より、JSの継承は全部でコンストラクタ継承、クラス継承、グループ継承、グループ寄生継承、ES 6のextens継承の5つの継承方式に分けられ、そのうちの4つは第3の最適化実現である.
    最後に、実現newキーワードの実現
    MDN:new演算子は、ユーザによって定義されたオブジェクトタイプの例または構築関数を有する内蔵オブジェクトの例を作成する.
    文法:new constructor[(argments)]
    function new(constructor, arguments) {
        let o = {}
        if (constructor && typeof constructor === 'function') {
            //          
            o.__proto__ = constructor.prototype
            //                 
            constructor.apply(o, arguments)
            return o
        }
    }