1. 程式人生 > >JavaScript各種繼承方式和優缺點

JavaScript各種繼承方式和優缺點

span 不同 pan 原型對象 解決 一份 type屬性 function new

好久沒寫博客啦,嘻嘻,這個月是2017年的最後一個月啦,大家應該都開始忙著寫年終總結了吧,嘻嘻,小穎今天給大家分享下Javascript中的幾種繼承方式以及他們的優缺點。

1.借助構造函數實現繼承

原理:通過call()函數修改 this 指向,從而實現將父類屬性掛載到子類實例中。

    function parent1() {
        this.name = ‘parent1‘;
    }
    function child1() {
        parent1.call(this);
        this.type = ‘child1‘;
    }
    console.log(
new child1);

打印結果:

技術分享圖片

當我們給父類 parent1 的 prototype 屬性添加say方法後,但是在 child1 中是獲取不到的。

    function parent1() {
        this.name = ‘parent1‘;
    }
    parent1.prototype.say = function() {
        console.log(‘hello‘);
    };

    function child1() {
        parent1.call(this);
        this
.type = ‘child1‘; } console.log(new child1, new child1().say());

打印結果:

技術分享圖片

所以.借助構造函數實現繼承,只能實現部分繼承;如果父類屬性都在構造函數中,則能夠實現全部繼承,如果父類原型對象上還有方法,則子類是繼承不到的。

總結:

優點:
1.只調用一次父類的構造函數,避免了在子類原型上創建不必要的,多余的屬性 。
2.原型鏈保持不變。
缺點:只能實現部分繼承;如果父類屬性都在構造函數中,則能夠實現全部繼承,如果父類原型對象上還有方法,則子類是繼承不到的。

2.借助原型鏈實現繼承(最通用的方式)

原理:將子類的prototype屬性賦值為父類實例對象,則子類的_proto_屬性繼承父類。

    function parent2() {
        this.name = ‘parent2‘;
        this.play = [1, 2, 3];
    }
    parent2.prototype.say = function() {
        console.log(‘hello‘);
    };

    function child2() {
        this.type = ‘child2‘;
    }
    child2.prototype = new parent2();
    console.log(new child2);
    var p1 = new child2();
    var p2 = new child2();
    console.log(p1.say());
    console.log(p1.play, p2.play);
    p1.play.push(4);
    console.log(p1, p2);
    console.log(p1.play, p2.play);

打印結果:

技術分享圖片

註意:

1.在第一種繼承方式中,子類是繼承不到父類 prototype 屬性的內容的,但現在可以繼承到了。

2.其實小穎只執行了 p1.play.push(4) ,然而 p2.play 的值也跟著變化了。

這其實都是因為 child2.prototype = new parent2(),他們的 __proto__ 都繼承了父類parent2 的所有屬性。雖然表面上 p1.play.push(4) 看起來像是只改變了 p1 的 play 屬性,但其實是改變了父類 parent2 的 play 屬性,而p1,p2繼承了 parent2 ,所以p1,p2同時發生變化。

總結:

優點:父類的方法(getName)得到了復用。
缺點:重寫子類的原型 等於 父類的一個實例,(父類的實例屬性變成子類的原型屬性)如果父類包含引用類型的屬性,那麽子類所有實例都會共享該屬性 (包含引用類型的*原型*屬性會被實例共享)。

3.組合方式

    function parent3() {
        this.name = ‘parent3‘;
        this.play = [1, 2, 3];
    }

    function child3() {
        parent3.call(this);
        this.type = ‘child3‘;
    }
    child3.prototype = new parent3();
    var p3 = new child3();
    var p4 = new child3();
    console.log(p3.play, p4.play);
    p3.play.push(4);
    console.log(p3,p4);
    console.log(p3.play, p4.play);

打印結果:

技術分享圖片

註意:

在上面的結果中,大家有沒有發現,同樣只給 p3.play.push(4) ,但是只有p3一個變了,但p4沒有變,其實大家通過小穎用紅框框起來的地方,大就會明白,為什麽p3、p4的 __proto__ 都繼承了父類parent2 的屬性,為什麽修改p3,p4,這次p4卻沒有變化。

總結:

優點:繼承了上述兩種方式的優點,摒棄了缺點,復用了方法,子類又有各自的屬性。
缺點:因為父類構造函數被執行了兩次,子類的原型對象(Sub.prototype)中也有一份父類的實例屬性,而且這些屬性會被子類實例(sub1,sub2)的屬性覆蓋掉,也存在內存浪費。

4.組合繼承的優化1

    function parent4() {
        this.name = ‘parent4‘;
        this.play = [1, 2, 3];
    }

    function child4() {
        parent4.call(this);
        this.type = ‘child4‘;
    }
    child4.prototype = parent4.prototype;
    var p5 = new child4();
    var p6 = new child4();
    console.log(p5, p6);
    console.log(p5 instanceof child4, p5 instanceof parent4);
    console.log(p5.constructor);

打印結果:

技術分享圖片

註意:

instanceof constructor 都是用來判斷一個實例對象是不是這個構造函數的實例的。
不同點是:用constructor 比instanceof 更嚴謹,例如如果 A 繼承 B,B 繼承 C,A 生成的實例對象,用 instanceof 判斷與 A、B、C 的關系,都是 true。所以無法區分這個到底是 A、B、C 誰生成的實例。而constructor 是原型對象的一個屬性,並且這個屬性的值是指向創建當前實例的對象的。

console.log(p5 instanceof child4, p5 instanceof parent4); 執行結果一樣,而且 p5.constructor 竟然不是 child4 而是 parent4。

5.組合繼承的優化2 ——寄生組合式繼承

   function parent5() {
        this.name = ‘parent5‘;
        this.play = [1, 2, 3];
    }

    function child5() {
        parent5.call(this);
        this.type = ‘child5‘;
    }
    child5.prototype = Object.create(parent5.prototype);
    child5.prototype.constructor = child5;
    var p7 = new child5();
    var p8 = new child5();
    console.log(p7, p8);
    console.log(p7.constructor);

打印結果:

技術分享圖片

總結:

組合繼承的缺點就是在繼承父類方法的時候調用了父類構造函數,從而造成內存浪費,並且找不到實例對象真正的 constructor

那在復用父類方法的時候,使用Object.create方法也可以達到目的,沒有調用父類構造函數,並將子類的 prototype.constructor 屬性賦值為自己本身,則問題完美解決。

JavaScript各種繼承方式和優缺點