JS基礎-Prototype原型繼承

概述

原型和閉包是JS的兩個難點,最近碰到了原型繼承的概念,正好在這裏總結一下。閉包

既然要實現繼承,就必定要有一個父類。函數

// 定義一個父類
        function father(name) {
            //屬性
            this.name = name;
        }
        // 原型方法
        father.prototype.getName = function () {
            return this.name;
        }

原型鏈繼承

基本思想就是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。性能

回顧一下原型、實例和構造函數的關係。this

每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象內部的指針。
// 子類
        function son(age) {
            // 屬性
            this.age = age;
        };

        son.prototype = new father('jason');

        son.prototype.getAge = function () {
            return this.age;
        }

        let firstchild = new son('19');

        console.log(firstchild.getAge()) // 19

這裏須要注意幾點的是:spa

  • 默認原型

原型鏈的最頂端是Object,全部引用類型默認都是繼承於Object的,因此默認也是有toString等方法的。prototype

clipboard.png

  • 如何肯定原型和實例的關係
    第一個方法是,instanceof,用於檢測實例與原型鏈中出現過的構造函數。
console.log(firstchild instanceof Object) //true
     console.log(firstchild instanceof son) //true
     console.log(firstchild instanceof father) //true
第二個方法是,isPrototypeOf方法。
console.log(Object.prototype.isPrototypeOf(firstchild)) //true
        console.log(son.prototype.isPrototypeOf(firstchild)) //true
        console.log(father.prototype.isPrototypeOf(firstchild)) //true
  • 謹慎定義方法

子類型可能要重寫父類型方法,或定義父類沒有的方法。無論是啥,這個方法必定要寫在替換原型語句的後面
還有原型鏈繼承的時候,不能使用對象字面量建立原型方法。設計

例如:指針

son.prototype = new father('jason');

        son.prototype = {
            getAge: function() {
                return this.age
            }
        }

這樣會致使建立一個新的Object實例,而非原來的father。code

  • 共享性和傳參問題

    第一,引用類型的原型屬性會被全部實例共享。對象

function father(name) {
            this.name = name;
            this.colors = ['blue', 'red', 'white'];
        }
        
        let firstchild = new son('19');
        let secondchild = new son('20');
        firstchild.colors.push("black");
        console.log(firstchild.colors) // ["blue", "red", "white", "black"]
        console.log(secondchild.colors) // ["blue", "red", "white", "black"]

第二,不能像父類型構造函數傳參數,書裏準確說法是,沒有辦法在不影響全部實例的狀況下,給父類構造函數傳遞參數。

小結

優勢:

  • 很是純粹的繼承關係,實例是子類的實例,也是父類的實例
  • 父類新增原型方法/原型屬性,子類都能訪問到
  • 簡單,易於實現

缺點:

  • 要想爲子類新增屬性和方法,必需要在new father()這樣的語句以後執行,不能放到構造器中
  • 沒法實現多繼承
  • 來自原型對象的引用屬性是全部實例共享的
  • 建立子類實例時,沒法向父類構造函數傳參

借用構造繼承

在子類型的構造函數中調用父類的構造函數,使用父類的構造函數來加強子類實例,等因而複製父類的實例屬性給子類(不用原型)

function son(age) {
            father.call(this);
            this.age = age;
        };

        son.prototype = new father('jason');
        
        son.prototype.getAge = function () {
            return this.age;
        }

        let firstchild = new son('19');
        let secondchild = new son('20');
        firstchild.colors.push("black");
        
        console.log(firstchild.colors); // ["blue", "red", "white", "black"]
        console.log(secondchild.colors); // ["blue", "red", "white"]
  • 能夠傳遞參數
  • 方法都在構造函數中定義,函數複用性丟失

總結

優勢:

  • 由例子可見,解決了1中子類實例共享父類引用屬性的問題
  • 建立子類實例時,能夠向父類傳遞參數
  • 能夠實現多繼承(call多個父類對象)

缺點:

  • 實例並非父類的實例,只是子類的實例
  • 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
  • 沒法實現函數複用,每一個子類都有父類實例函數的副本,影響性能

組合繼承

也就是將原型鏈繼承和構造函數繼承融合,原型鏈實現對原型屬性和方法的繼承,構造函數實現對實例屬性的繼承。
這樣既保證了原型上函數的複用,也保證了每一個實例有本身的屬性。

function son(name, age) {
            father.call(this, name);
            this.age = age;
        };

        son.prototype = new father();
        
        son.prototype.getAge = function () {
            return this.age;
        }


        let firstchild = new son('jason', '19');
        let secondchild = new son('jason junior', '18');
        firstchild.colors.push("black");
        
        
        console.log(firstchild.colors); // ["blue", "red", "white", "black"]
        console.log(secondchild.colors); //["blue", "red", "white"]
        console.log(firstchild.getName()); // jason
        console.log(secondchild.getName()); // jason junior
        console.log(firstchild.getAge()); //19
        console.log(secondchild.getAge()); //18

特色:

  • 能夠繼承實例屬性/方法,也能夠繼承原型屬性/方法
  • 既是子類的實例,也是父類的實例
  • 不存在引用屬性共享問題
  • 可傳參
  • 函數可複用

缺點:

  • 調用了兩次父類構造函數,生成了兩份實例(子類實例將子類原型上的那份屏蔽了)

原型式繼承

爲父類實例添加新特性,做爲子類實例返回

let p = {
            name: 'jason',
            colors: ['white', 'black', 'red']
        }
        function object (o) {
            function F() {};
            F.prototype = o;
            return new F();
        }

        let firstchild = object(p)
        let secondchild = object(p)

        firstchild.name = 'jason1'
        firstchild.colors.push('blue')

        secondchild.name = 'jason2'
        secondchild.colors.push('green')

        console.log(p.colors) // ["white", "black", "red", "blue", "green"]

ECMAScript 5新增Object.create()方法規範原型式繼承。兩個參數,一個參數是新對象原型的對象,一個參數是對象定義額外屬性的對象,第二個可忽略,就等於上述object函數了

寄生式繼承

創造一個用於封裝繼承過程的函數,該函數內部以某種方式加強對象。

function create(o) {
            let clone = object(o);
            o.sayHi = function () {
                console.log('Hi')
            }
            return o;
        }

寄生組合繼承

組合繼承雖然好用,可是也有缺陷,就是會調用兩次構造函數,一次在建立時候,一次在內部,那個call方法。

所謂寄生組合繼承,即經過借用構造函數方式,繼承屬性,經過原型鍊形式繼承方法。

沿用寄生方式:

function inheritPrototype (sub, sup) {
            let prototype = object(sup.prototype);
            prototype.constructor = sub;
            sub.prototype = prototype;
        }
function father(name) {
            this.name = name;
            this.colors = ['blue', 'red', 'white'];
        }

        father.prototype.getName = function () {
            return this.name;
        }

        function son(name, age) {
            father.call(this, name);
            this.age = age;
        };

        function object (o) {
            function F() {};
            F.prototype = o;
            return new F();
        }

        function inheritPrototype (sub, super) {
            let prototype = object(super.prototype);
            prototype.constructor = sub;
            sub.prototype = prototype;
        }

        inheritPrototype(son, father);

        son.prototype.getAge = function () {
            return this.age;
        }

總結

優勢:

  • 堪稱完美

缺點:

  • 實現較爲複雜
參考 <<JavaScript高級程序設計>>總結
相關文章
相關標籤/搜索