JavaScript各種繼承方式和優缺點
好久沒寫博客啦,嘻嘻,這個月是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各種繼承方式和優缺點