類式繼承(構造函數)
JS中其實是沒有類的概念的,所謂的類也是模擬出來的。特別是當我們是用new 關鍵字的時候,就使得“類”的概念就越像其他語言中的類了。類式繼承是在函數對象內調用父類的構造函數,使得自身獲得父類的方法和屬性。call和apply方法為類式繼承提供了支持。通過改變this的作用環境,使得子類本身具有父類的各種屬性。
var father = function() {this.age = 52;this.say = function() {alert('hello i am '+ this.name ' and i am '+this.age + 'years old');}}var child = function() {this.name = 'bill';father.call(this);}var man = new child();man.say();原型繼承
原型繼承在開發中經常用到。它有別于類繼承是因為繼承不在對象本身,而在對象的原型上(prototype)。每一個對象都有原型,在瀏覽器中它體現在一個隱藏的__proto__屬性上。在一些現代瀏覽器中你可以更改它們。比如在zepto中,就是通過添加zepto的fn對象到一個空的數組的__proto__屬性上去,從而使得該數組成為一個zepto對象并且擁有所有的方法。話說回來,當一個對象需要調用某個方法時,它回去最近的原型上查找該方法,如果沒有找到,它會再次往下繼續查找。這樣逐級查找,一直找到了要找的方法。 這些查找的原型構成了該對象的原型鏈條。原型最后指向的是null。我們說的原型繼承,就是將父對像的方法給子類的原型。子類的構造函數中不擁有這些方法和屬性。
var father = function() {}father.prototype.a = function() {}var child = function(){}//開始繼承child.prototype = new father();var man = new child();man.a();可以看到第七行實現了原型繼承。很多人并不陌生這種方式。通過在瀏覽器中打印man我們就可以查看各個原型的繼承關系。

可以看到逐級的關系child->object(father實例化的對象)->father。child是通過中間層繼承了father的原型上的東西的。但是為什么中間還有一層object呢, 為什么不把child.prototype = father.prototype。 答案是如果這樣做child和father就沒有區別了。大家應該還記得在prototype中有個constructor屬性,指向的是構造函數。按照正常的情況我們要把constructor的值改回來指向child的構造函數。但如果直接把father.prototype賦值給child.prototype,那么constructor應該指向誰呢?所以很顯然只能通過中間層才能使得child和father保持為獨立的對象。
類式繼承和原型繼承的對比
構造函數(類)式繼承
首先,構造函數繼承的方法都會存在父對象之中,每一次實例,都會將funciton保存在內存中,這樣的做法毫無以為會帶來性能上的問題。
其次,類式繼承是不可變的。無法復用,在運行時,無法修改或者添加新的方法,這種方式是一種固步自封的死方法。實踐中很少單純使用。
原型繼承
優點:
原型鏈可改變:原型鏈上的父類可替換可擴展
可以通過改變原型鏈接而對子類進行修改的。另外就是類式繼承不支持多重繼承,而對于原型繼承來說,你只需要寫好extend對對象進行擴展即可。
但是原型鏈繼承也有2個問題。
第一,包含引用類型值的原型屬性會被所有實例共享(可以這樣理解:執行sub1.arr.push(2);先對sub1進行屬性查找,找遍了實例屬性(在本例中沒有實例屬性),沒找到,就開始順著原型鏈向上找,拿到了sub1的原型對象,一搜身,發現有arr屬性。于是給arr末尾插入了2,所以sub2.arr也變了)。
第二,在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。(實際上,應該說沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數傳遞參數)實踐中很少單純使用原型鏈。
function Super(){this.val = 1;this.arr = [1];}function Sub(){// ...}Sub.prototype = new Super(); // 核心var sub1 = new Sub();var sub2 = new Sub();sub1.val = 2;sub1.arr.push(2);alert(sub1.val); // 2alert(sub2.val); // 1alert(sub1.arr); // 1, 2alert(sub2.arr); // 1, 2總結:
類式繼承在實例化時,父類可傳參,不能復用(父類不可變,每一次實例都會將父類內容保存在內存中)
原型繼承在實例化時,父類不可傳參,可以復用(原型鏈可改變(父類可替換可擴展),父類不會保存在內存中,而是順著原型鏈查找,但是結果是原型屬性會被所有實例共享(尤其影響引用類型值))
組合繼承(最常用)
組合繼承將原型鏈和借用構造函數的技術結合到一起,發揮兩者之長的一種繼承模式。
思路是使用原型鏈實現對原型屬性和方法的繼承,通過借用構造函數實現對實例屬性的繼承。
function SuperType(name){this.name = name;this.numbers = [1,2,3];}SuperType.prototype.sayName = function(){console.log(this.name);}function SubType(name,age){SuperType.call(this,name);this.age = age;}SubType.prototype = new SuperType();SubType.prototype.sayAge = function(){console.log(this.age);}var instance1 = new SubType('aaa',21);instance1.numbers.push(666);console.log(instance1.numbers);instance1.sayName();instance1.sayAge();var instance2 = new SubType('bbb',22);console.log(instance2.numbers);instance2.sayName();instance2.sayAge();把實例函數都放在原型對象上,通過Sub.prototype = new Super();繼承父類函數,以實現函數復用。
保留借用構造函數方式的優點,通過Super.call(this);繼承父類的基本屬性和引用屬性,以實現傳參;
優缺點
優點:
缺點:
(一點小瑕疵)子類原型上有一份多余的父類實例屬性,因為父類構造函數被調用了兩次,生成了兩份,而子類實例上的那一份屏蔽了子類原型上的。。。又是內存浪費,比剛才情況好點,不過確實是瑕疵。
以上所述是小編給大家介紹的JavaScript的六種繼承方式,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網網站的支持!
|
新聞熱點
疑難解答