寫在前面
繼承的簡介
“繼承”是JavaScript物件導向設計的重要一環,願你認真讀完本文,吃透繼承的概念。
繼承的核心
在js中,繼承主要是依靠原型鏈實現的,原型鏈是通往繼承的必經之路。你可以參考《一張圖徹底KO原型鏈(prototype,__proto__)》這篇博文,相信它一定能很好地幫助你原型鏈。
1. 繼承方式一:原型鏈
1.1 介紹
原型鏈是實現繼承最原始的模式,即通過prototype屬性實現繼承。
//父級-建構函式
function Father() {
this.fatherProp = true
}
//父級-原型屬性
Father.prototype.getFatherValue = function() {
return this.fatherProp
}
//子級-建構函式
function Son() {
this.sonProp = false
}
//子級-原型屬性:繼承父級
//即__proto__指向父級的prototype
//若不理解請閱讀《一張圖徹底KO原型鏈(prototype,__proto__》
Son.prototype = new Father()
//子級-新增原型方法
Son.prototype.getSonValue = function() {
return this.sonProp
}
//建立子級的例項物件
var son = new Son()
console.log(son.getFatherValue()) //true
複製程式碼
1.2 解析:son例項物件是如何找到getFatherValue()方法的呢?
- 首先在son物件自身中找。若物件自身沒找到
- 然後在Son.prototype中找。若Son.prototype中沒找到
- 繼續往上一層,Son.prototype.__proto__(Fater.prototype)
- 依次類推,直到找到需要的屬性或方法,或到達原型鏈頂端Object.prototype
如果到最後都沒找到,會發生什麼呢?
//一個不存在的方法
console.log(son.getValue()) //ERROE:not a function
複製程式碼
1.3 注意事項
-
重寫父級原型鏈的方法或者新增父級原型鏈不存在的方法,必須在父級原型鏈程式碼之後。(這個很好理解,不放程式碼演示了)
-
通過原型鏈實現繼承後,不能再使用字面量的方式建立原型物件,因為會覆蓋原型鏈。
//子級-原型屬性:繼承父級
Son.prototype = new Father()
//不能像下面這樣,這樣會使得上面的程式碼無效
//因為這相當於重新建立了一個原型物件
Son.prototype = {
getSonValue: function() {
return this.sonProp
}
}
複製程式碼
1.4 原型鏈實現繼承的弊端
世間萬事萬物都不可能十全而十美,原型鏈雖然強大,但也存在缺陷。
- 原型鏈中引用型別的屬性會被所有例項共享的,即所有例項物件使用的是同一份資料,會相互影響。
function Father() {
this.arr = [1,2,3]
}
function Son() {
}
Son.prototype = new Father()
var son1 = new Son()
console.log(son1.arr) //1,2,3
var son2 = new Son()
son2.arr.push(4)
console.log(son2.arr) //1,2,3,4
console.log(son1.arr) //1,2,3,4
複製程式碼
- 無法向父級建構函式傳參
2. 繼承方式二:借用建構函式
2.1 介紹
方式一中引用型別帶來的問題可借用建構函式的方式解決。其核心思想是:在子級建構函式中呼叫父級建構函式。
如何實現在一個建構函式中呼叫另一個函式?——call()和apply()
function Father() {
this.arr = [1,2,3]
}
function Son() {
//call的第一個函式是this指向的物件,即建構函式的例項物件
Father.call(this)
/*上面程式碼等同於下面這段程式碼:
(function() {
this.arr = [1,2,3]
}).call(this)
*/
}
var son1 = new Son()
console.log(son1.arr) //1,2,3
var son2 = new Son()
son2.arr.push(4)
console.log(son2.arr) //1,2,3,4
console.log(son1.arr) //1,2,3
複製程式碼
//解決傳參問題:
function Father(name) {
this.name = name
}
function Son(name) {
Father.call(this, name)
}
var son1 = new Son("小名")
console.log(son1.name) //小名
var son2 = new Son("一燈")
console.log(son2.name) //一燈
複製程式碼
2.2 借用建構函式的缺陷
這種方式是通過建構函式實現的,當然也把建構函式自身的問題帶過來了——破壞了複用性。因為每個例項都建立了一份副本。
3. 組合繼承
3.1 介紹
組合繼承 = 原型鏈 + 借用建構函式。取其長避其短:共享的用原型鏈,各自的借用建構函式
function Father(name) {
this.name = name
this.arr = [1,2,3]
}
Father.prototype.getName = function() {
console.log(this.name)
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = new Father()
Son.prototype.constructor = Son
Son.prototype.getAge = function() {
console.log(this.age)
}
var son1 = new Son("小名", 23)
son1.arr.push(4)
console.log(son1.arr) //1,2,3,4
son1.getName() //小名
son1.getAge() //23
var son2 = new Son("一燈", 24)
console.log(son2.arr) //1,2,3
son1.getName() //一燈
son1.getAge() //24
複製程式碼
3.2 解析
- 借用建構函式部分:
Father.call(this, name)——name來自Father
this.age = age; Son.prototype.constructor = Son——age來自Son
- 原型鏈部分:
Father.prototype.getName——getName方法來自Father.prototype
Son.prototype.getAge——getAge來自Son.prototype
後記
關於繼承的後三種方式馬上推出,期待你的點贊&關注!