學過java或者c#之類語言的同窗,應該會對js的繼承感到很困惑--不要問我怎麼知道的,js的繼承主要是基於原型(prototype)的,對js的原型感興趣的同窗,
能夠了解一下我以前寫的 JS中的原型對象
相信不少同窗也跟我同樣,剛開始接觸js的面向對象編程的時候,都抱着一種排斥的心態--爲何js這麼麻煩?
其實瞭解完原型鏈後,再來看js的繼承,你會發現js的繼承其實比其餘OOP語言更簡單,更靈活,咱們來看一個基於原型鏈的繼承前端
// 父類 function Person() {} // 子類 function Student(){} // 繼承 Student.prototype = new Person()
咱們只要把子類的prototype設置爲父類的實例
,就完成了繼承,怎麼樣? 是否是超級簡單? 有沒有比Java,C#的清晰?
事實上,以上就是js裏面的原型鏈繼承java
固然,經過以上代碼,咱們的Student只是繼承了一個空殼的Person,這樣視乎是毫無心義的,咱們使用繼承的目的,
就是要經過繼承獲取父類的內容,那咱們先給父類加上一點點簡單的內容(新增的地方標記 '// 新增的代碼'):git
// 父類 function Person(name,age) { this.name = name || 'unknow' // 新增的代碼 this.age = age || 0 // 新增的代碼 } // 子類 function Student(name){ this.name = name // 新增的代碼 this.score = 80 // 新增的代碼 } // 繼承 Student.prototype = new Person()
使用github
var stu = new Student('lucy') console.log(stu.name) // lucy --子類覆蓋父類的屬性 console.log(stu.age) // 0 --父類的屬性 console.log(stu.score) // 80 --子類本身的屬性
這裏爲了下降複雜度,咱們只演示了普通屬性的繼承,沒有演示方法的繼承,事實上,方法的繼承也很簡單,
咱們再來稍微修改一下代碼,基於上面的代碼,給父類和子類分別加一個方法(新增的地方標記 '// 新增的代碼')編程
// 父類 function Person(name,age) { this.name = name || 'unknow' this.age = age || 0 } // 爲父類新曾一個方法 Person.prototype.say = function() { // 新增的代碼 console.log('I am a person') } // 子類 function Student(name){ this.name = name this.score = 80 } // 繼承 注意,繼承必需要寫在子類方法定義的前面 Student.prototype = new Person() // 爲子類新增一個方法(在繼承以後,不然會被覆蓋) Student.prototype.study = function () { // 新增的代碼 console.log('I am studing') }
使用c#
var stu = new Student('lucy') console.log(stu.name) // lucy --子類覆蓋父類的屬性 console.log(stu.age) // 0 --父類的屬性 console.log(stu.score) // 80 --子類本身的屬性 stu.say() // I am a person --繼承自父類的方法 stu.study() // I am studing --子類本身的方法
這樣,看起來咱們好像已經完成了一個完整的繼承了,這個就是原型鏈繼承,怎麼樣,是否是很好理解?
可是,原型鏈繼承有一個缺點,就是屬性若是是引用類型的話,會共享引用類型,請看如下代碼函數
// 父類 function Person() { this.hobbies = ['music','reading'] } // 子類 function Student(){} // 繼承 Student.prototype = new Person()
使用學習
var stu1 = new Student() var stu2 = new Student() stu1.hobbies.push('basketball') console.log(stu1.hobbies) // music,reading,basketball console.log(stu2.hobbies) // music,reading,basketball
咱們能夠看到,當咱們改變stu1的引用類型的屬性時,stu2對應的屬性,也會跟着更改,這就是原型鏈繼承缺點 --引用屬性會被全部實例共享,
那咱們如何解決這個問題呢? 就是下面咱們要提到的借用構造函數繼承,咱們來看一下使用構造函數繼承的最簡單例子:this
// 父類 function Person() { this.hobbies = ['music','reading'] } // 子類 function Student(){ Person.call(this) // 新增的代碼 }
使用prototype
var stu1 = new Student() var stu2 = new Student() stu1.hobbies.push('basketball') console.log(stu1.hobbies) // music,reading,basketball console.log(stu2.hobbies) // music,reading
這樣,咱們就解決了引用類型被全部實例共享的問題了
注意,這裏跟 原型鏈繼承 有個比較明顯的區別是並無使用prototype繼承,而是在子類裏面執行父類的構造函數,
至關於把父類的代碼複製到子類裏面執行一遍,這樣作的另外一個好處就是能夠給父類傳參
// 父類 function Person(name) { this.name = name // 新增的代碼 } // 子類 function Student(name){ Person.call(this,name) // 改動的代碼 }
使用
var stu1 = new Student('lucy') var stu2 = new Student('lili') console.log(stu1.name) // lucy console.log(stu2.name) // lili
看起來已經頗有Java,C#的味道了有沒有?
可是,構造函數解決了引用類型被全部實例共享的問題,但正是由於解決了這個問題,致使一個很矛盾的問題出現了,--函數也是引用類型,
也沒辦法共享了.也就是說,每一個實例裏面的函數,雖然功能同樣,可是卻不是同一個函數,就至關於咱們每實例化一個子類,就複製了一遍的函數代碼
// 父類 function Person(name) { this.say = function() {} // 改動的代碼 } // 子類 function Student(name){ Person.call(this,name) }
使用
var stu1 = new Student('lucy') var stu2 = new Student('lili') console.log(stu1.say === stu2.say) // false
以上代碼能夠證實,父類的函數,在子類的實例下是不共享的
繼承方式 | 繼承核心代碼 | 優缺點 |
---|---|---|
原型鏈繼承 | Student.prototype = new Person() |
實例的引用類型共享 |
構造函數繼承 | 在子類(Student)裏執行 Person.call(this) |
實例的引用類型不共享 |
從上表咱們能夠看出 原型鏈繼承
和 構造函數繼承
這兩種繼承方式的優缺點恰好是互相矛盾的,那麼咱們有沒有辦法魚和熊掌兼得呢?
沒有的話,我就不會說出來了,^_^,接下來請容許我隆重介紹 組合繼承
組合繼承,就是各取上面2種繼承的長處,普通屬性 使用 構造函數繼承
,函數 使用 原型鏈繼承
,
這個代碼稍微複雜一點,不過相信有了上面的基礎後,看起來也是很輕鬆
// 父類 function Person() { this.hobbies = ['music','reading'] } // 父類函數 Person.prototype.say = function() {console.log('I am a person')} // 子類 function Student(){ Person.call(this) // 構造函數繼承(繼承屬性) } // 繼承 Student.prototype = new Person() // 原型鏈繼承(繼承方法)
使用
// 實例化 var stu1 = new Student() var stu2 = new Student() stu1.hobbies.push('basketball') console.log(stu1.hobbies) // music,reading,basketball console.log(stu2.hobbies) // music,reading console.log(stu1.say == stu2.say) // true
這樣,咱們就既能實現屬性的獨立,又能作到函數的共享,是否是很完美呢?
組合繼承聽說是JavaScript中最經常使用的繼承方式(具體沒法考證哈).
至此,咱們就把js裏面的經常使用繼承瞭解完了,其實也沒有那麼難嘛!不過,咱們總結一下3種繼承
上面爲何要說相對完美呢? 由於本文的標題叫【JS中的繼承(上)】,那確定是還有
【JS中的繼承(下)】.md)咯,
目前爲止,咱們只講了3種最基本的繼承,事實上,JavaScript還有好多繼承方式,爲了讓你不至於學習疲勞,因此我打算分開來說,
若是你沒有那個耐性繼續看下去,那麼看完這篇對於理解JavaScript的繼承,也是夠用的。可是建議多看兩遍,加深印象,
我學js繼承的時候,那本犀牛書都被我翻爛了,寫這篇文字的時候,我還在一遍翻一邊寫的呢(噓!)
好了,今天就到這裏,感謝收看,若是以爲對您有用,請給本文的github加個star,萬分感謝,另外,github上還有其餘一些關於前端的教程和組件,有興趣的童鞋能夠看看,大家的支持就是我最大的動力。