本文將從如下幾方面介紹類與繼承html
類的聲明通常有兩種方式瀏覽器
//類的聲明 var Animal = function () { this.name = 'Animal'; }; //ES6中類的聲明 class Animal2 { constructor () { this.name = 'Animal2'; } }
實例化就比較簡單,直接用new運算符閉包
new Animall() new Animal2()
這些比較簡單,簡單介紹一下就能夠了。接下來,介紹本文的重點內容,繼承。app
實現繼承的方式主要有兩種:函數
第一種藉助構造函數實現繼承測試
先看個了例子優化
function Parent1 () { this.name = 'parent1'; } function Child1 () { Parent1.call(this); //這裏的call用apply也能夠 this.type = 'child1'; } console.log(new Child1());
輸出結果this
能夠看到,生成Child1裏面有了父級的屬性name,實現了繼承。爲何就實現繼承了呢?spa
由於在Child1裏執行了這句 Parent1.call(this); 若是對this不理解的話,建議看看這個JavaScript做用域和閉包prototype
在子類的函數體裏執行父級的構造函數,同時改變函數運行的上下文環境(也就是this的指向),使this指向Child1這個類,從而致使了父類的屬性都會掛載到子類這個類上去,如此便實現了繼承。
但這種繼承的方法有一個缺點,它只是把父類中的屬性繼承了,但父類的原型中的屬性繼承不了。繼續上面的代碼
Parent1.prototype.say = function () { console.log("Parent1 prototype") }; new Child1().say()
從結果中能夠看出 Child1中是沒有say方法的,由於say是加在父類的原型上的,這種繼承方式只改變父類構造函數在子類函數體中的指向,繼承不了原型的屬性。
第二種是藉助原型鏈實現繼承
原型鏈這裏直接用了,再也不詳細介紹了,若是對原型鏈還不是很瞭解的話,建議先看看這個,詳談Javascript原型鏈
function Parent2 () { this.name = 'parent2'; this.play = [1, 2, 3]; } function Child2 () { this.type = 'child2'; } Child2.prototype = new Parent2(); //經過把Child2的原型指向Parent2來實現繼承
在瀏覽器中檢驗一下
能夠看到在Child2的實例的__proto__的屬性中有Parent2的屬性,由此實現了Child2從Parent2的繼承。
但這種繼承方式也有不足。接着看代碼
var s1 = new Child2(); var s2 = new Child2(); s1.play.push(4);
console.log('s1.play:'+s1.play);
console.log('s2.play:'+s2.play);
打印結果
咱們只改了s1這個實例的屬性,卻發現Child2的其餘實例的屬性都一塊兒改變了,由於s1修改的是它原型的屬性,原型的屬性修改,全部繼承自該原型的類的屬性都會一塊兒改變,所以Child2的實例之間並無隔離開來,這顯然不是咱們想要的。
第三種 組合方式
組合方式就是前兩種方法組合而成的,上面兩種方式都有不足,這種方式就解決了上面兩種方式的不足。
看代碼
function Parent3 () { this.name = 'parent3'; this.play = [1, 2, 3]; } function Child3 () { Parent3.call(this); //子類裏執行父類構造函數 this.type = 'child3'; } Child3.prototype = new Parent3(); //子類的原型指向父類 //如下是測試代碼 var s3 = new Child3(); var s4 = new Child3(); s3.play.push(4); console.log(s3.play, s4.play);
打印結果
能夠看出,修改某個實例的屬性,並不會引發父類的屬性的變化。
這種方式的繼承把構造函數和原型鏈的繼承的方式的優勢結合起來,並彌補了兩者的不足,功能上已經沒有缺點了。
但這種方法仍不完美,由於建立一個子類的實例的時候,父類的構造函數執行了兩次。
每一次建立實例,都會執行兩次構造函數這是沒有必要的,由於在繼承構造函數的時侯,也就是Parent3.call(this)的時候,parnet的屬性已經在child裏運行了,外面原型鏈繼承的時候就沒有必要再執行一次了。因此,接下來咱們對這一方法再作一個優化。
第四種 組合方式的優化
上面一種繼承方式問題出在繼承原型的時候又一次執行了父類的構造函數,因此優化就從這一點出發。
組合方式中爲了解決藉助構造函數繼承(也就是本文中第一種)的缺點,父類的原型中的屬性繼承不了,因此才把子類的原型指向了父類。
可是父類的屬性,在子類已經中已經存在了,子類只是缺乏父類的原型中的屬性,因此,根據這一點,咱們作出優化。
function Parent4 () { this.name = 'parent4'; this.play = [1, 2, 3]; } function Child4 () { Parent4.call(this); this.type = 'child4'; } Child4.prototype = Parent4.prototype; //優化的點在這裏 //如下爲測試代碼 var s5 = new Child4(); var s6 = new Child4(); console.log(s5, s6); console.log(s5 instanceof Child4, s5 instanceof Parent4); console.log(s5.constructor);
在這種繼承方式中,並無把直接把子類的原型指向父類,而是指向了父類的原型。這樣就避免了父類構造函數的二次執行,從而完成了針對組合方式的優化。但仍是有一點小問題,先看輸出結果
能夠看到s5是new Child4()出來的,可是他的constructor倒是Parent4.
這是由於Child4這個類中並無構造函數,它的構造函數是從原型鏈中的上一級拿過來的,也就是Parent4。因此說到這裏,終於能把最完美的繼承方式接受給你們啦。
接下來。。。
第五種 組合的完美優化
先看代碼吧
function Parent5 () { this.name = 'parent5'; this.play = [1, 2, 3]; } function Child5 () { Parent5.call(this); this.type = 'child5'; } //把子類的原型指向經過Object.create建立的中間對象 Child5.prototype = Object.create(Parent5.prototype); //把Child5的原型的構造函數指向本身 Child5.prototype.constructor = Child5; //測試 var s7= new Child5(); console.log(s7 instanceof Child5, s7 instanceof Parent5) console.log(s7.constructor);
本例中經過把子類的原型指向Object.create(Parent5.prototype),實現了子類和父類構造函數的分離,可是這時子類中仍是沒有本身的構造函數,因此緊接着又設置了子類的構造函數,由此實現了完美的組合繼承。
測試結果
本文並無直接把最完美的繼承直接寫出來,而是由淺入深按部就班的來介紹的,若是對後面幾種方法沒看太懂的話,多是原型鏈掌握的不夠好,仍是建議看看這個詳談Javascript原型鏈。
================
class A { constructor(name, age) { this.name = name; this.age = age; } getName() { return this.name; } } class B extends A { constructor(name, age) { super(name, age); this.job = "IT"; } getJob() { return this.job; } getNameAndJob() { return super.getName() + this.job; } } var b = new B("Tom", 20); console.log(b.name); console.log(b.age); console.log(b.getName()); console.log(b.getJob()); console.log(b.getNameAndJob()); //輸出:Tom,20,Tom,IT,TomIT