Javascript原型、原型鏈、繼承

對象屬性 __proto__ prototype
普通對象 ×
函數對象
function Person(name,age){
        this.name = name;
        this.age = age;
        Person.prototype.say = function(){
            console.log(`my name is ${this.name} and ${this.age} years`)
        }
    }

    let p1 = new Person('pfzz',25)

p1是普通對象
Person則是函數對象java

// prototype是函數對象的屬性,其本質是 一個對象,包含了__proto__與constructor,
// 只看普通對象p1,p1只有__proto__,而且指向建立它的函數對象的prototype
    console.info(p1)
    console.info(p1.__proto__ === Person.prototype)
    console.info(p1.__proto__.constructor === Person)
    console.info(p1.__proto__.__proto__ === Object.prototype)
    console.info(p1.__proto__.__proto__.constructor === Object)
    console.info(p1.__proto__.__proto__.__proto__ === null)



    // 函數對象Person,有__proto__、prototype屬性
    console.info(Person.__proto__ === Function.prototype)
    console.info(Person.__proto__.constructor === Function)
    console.info(Person.__proto__.__proto__ === Object.prototype)
    console.info(Person.__proto__.__proto__.constructor === Object)
    console.info(Person.__proto__.__proto__.__proto__ === null)

    console.info(Person.prototype.constructor === Person)
    console.info(Person.prototype.__proto__ === Object.prototype)
    console.info(Person.prototype.__proto__.constructor === Object)
    console.info(Person.prototype.__proto__.__proto__ === null)
    
    // 綜上具備在指向的只有__proto__與constructor屬性,
    // 而constructor屬性老是指向prototype所在的函數對象,造成閉環。
    // __proto__最終老是指向null

由上可知javasript的繼承就要靠__proto__來實現es6

// 一、類式繼承
    function Child1(name,age,sex){
        this.sex = sex;
        const prototype = new Person(name,age);
        prototype.play = function(){
            console.log(`my name is ${this.name} and ${this.age} years and ${this.sex}`)
        };
        this.__proto__ = prototype
    }
    const kind1 = new Child1('pfzzz',25,'man')
    kind1.say()
    console.info(kind1)
    console.info(kind1.__proto__ !== Child1.prototype)
    缺點:繼承屬性被放在原型鏈上,致使kind1.__proto__ !== Child1.prototype
// 二、構造函數式繼承
    function Child2(name,age,sex){
        this.sex = sex;
        Person.call(this,name,age)
    }

    const kind2 = new Child2('pfzzz',25,'man')
    console.info(kind2)
    kind2.say() // 報錯
    缺點:原型鏈上的方法沒有被繼承
// 三、組合式繼承
    function Child3(name,age,sex){
        this.sex = sex;
        Person.call(this,name,age)  // 第一次
    };
    Child3.prototype = new Person() // 第二次
     
    const kind3 = new Child3('pfzzz',25,'man')
    console.info(kind3)
    kind3.say()
    缺點:子類沒法傳遞動態參數給父類,且父類構造函數被調用兩次,問題不大。
// 四、野路子繼承
    function inherit(child, parent) {
        // 將父類原型和子類原型合併,並賦值給子類的原型
        child.prototype = Object.assign(Object.create(parent.prototype), child.prototype)
        // 重寫被污染的子類的constructor
        p.constructor = child
    }
//  缺點:Object.assign並非最好的方法
//五、利用es5的Reflect來優化
    function fancyShadowMerge(target, source) {
        for (const key of Reflect.ownKeys(source)) {
            Reflect.defineProperty(target, key, Reflect.getOwnPropertyDescriptor(source, key))
        }
        return target
    }

    // Core
    function inherit(child, parent) {
        const objectPrototype = Object.prototype
        // 繼承父類的原型
        const parentPrototype = Object.create(parent.prototype)
        let childPrototype = child.prototype
        // 若子類沒有繼承任何類,直接合並子類原型和父類原型上的全部方法
        // 包含可枚舉/不可枚舉的方法
        if (Reflect.getPrototypeOf(childPrototype) === objectPrototype) {
            child.prototype = fancyShadowMerge(parentPrototype, childPrototype)
        } else {
            // 若子類已經繼承子某個類
            // 父類的原型將在子類原型鏈的盡頭補全
            while (Reflect.getPrototypeOf(childPrototype) !== objectPrototype) {
            childPrototype = Reflect.getPrototypeOf(childPrototype)
            }
        Reflect.setPrototypeOf(childPrototype, parent.prototype)
        }
        // 重寫被污染的子類的constructor
        parentPrototype.constructor = child
    }

其餘還有寄生式繼承,寄生組合式繼承,但都有一些反作用。
這些都是es5的繼承,如今有了es6的 class,相關繼承就不須要在過多考慮了,這些屬於拓展知識,知道最好不知道也不影響代碼書寫。函數

相關文章
相關標籤/搜索