js中實現繼承的方法
阿新 • • 發佈:2020-07-18
[TOC]
## 借用建構函式
這種技術的基本思想很簡單,就是在子型別建構函式的內部呼叫超型別的建構函式。另外,函式只不過是在特定環境中執行程式碼的物件,因此通過使用apply()和call()方法也可以在新建立的物件上執行建構函式。
``` js
function Box(name){
this.name = name
}
Box.prototype.age = 18
function Desk(name){
Box.call(this, name) // 物件冒充,物件冒充只能繼承構造裡的資訊
}
var desk = new Desk('ccc')
console.log(desk.name) // --> ccc
console.log(desk.age) // --> undefined
```
從中可以看到,繼承來的只有例項屬性,而原型上的屬性是訪問不到的。這種模式解決了兩個問題,就是可以傳參,可以繼承,但是沒有原型,就沒有辦法複用。
## 組合繼承
``` js
function Box(name){
this.name = name
}
Box.prototype.run = function (){
console.log(this.name + '正在執行...')
}
function Desk(name){
Box.call(this, name) // 物件冒充
}
Desk.prototype = new Box() // 原型鏈
var desk = new Desk('ccc')
console.log(desk.name) // --> ccc
desk.run() // --> ccc正在執行...
```
這種繼承方式的思路是:用使用原型鏈的方式來實現對原型屬性和方法的繼承,而通過借用建構函式來實現對例項屬性的繼承。
## 原型式繼承
原型式繼承:是藉助原型可以基於已有的物件建立新物件,同時還不必因此建立自定義型別。講到這裡必須得提到一個人,道格拉斯·克羅克福德在2006年寫的一篇文章《Prototype inheritance in Javascript》(Javascript中的原型式繼承)中給出了一個方法:
``` js
function object(o) { //傳遞一個字面量函式
function F(){} //建立一個建構函式
F.prototype = o; //把字面量函式賦值給建構函式的原型
return new F() //最終返回出例項化的建構函式
}
```
看如下的例子:
``` js
function obj(o) {
function F (){}
F.prototype = o;
return new F()
}
var box = {
name: 'ccc',
age: 18,
family: ['哥哥','姐姐']
}
var box1 = obj(box)
console.log(box1.name) // --> ccc
box1.family.push('妹妹')
console.log(box1.family) // --> ["哥哥", "姐姐", "妹妹"]
var box2 = obj(box)
console.log(box2.family) // --> ["哥哥", "姐姐", "妹妹"]
```
因為上述的程式碼的實現邏輯跟原型鏈繼承很類似,所以裡面的引用陣列,即family屬性被共享了。
## 寄生式繼承
``` js
function obj(o) {
function F (){}
F.prototype = o;
return new F()
}
function create(o){
var clone = obj(o) // 通過呼叫函式建立一個新物件
clone.sayName = function(){ // 以某種方式來增強這個物件
console.log('hi')
}
return clone // 返回這個物件
}
var person = {
name: 'ccc',
friends: ['aa','bb']
}
var anotherPerson = create(person)
anotherPerson.sayName() // --> hi
```
這個例子中的程式碼基於person返回一個新物件————anotherPerson。新物件不僅具有person的所有屬性和方法,而且還有自己的sayHi()方法。在主要考慮物件而不是自定義型別和建構函式的情況下,寄生式繼承也是一種有用的模式。使用寄生式繼承來為物件新增函式,會由於不能做到函式複用而降低效率,這一點與建構函式模式類似。
## 寄生組合式繼承
前面說過,組合繼承是Javascript最常用的繼承模式,不過,它也有自己的不足。組合繼承最大的問題就是無論什麼情況下,都會呼叫過兩次超型別建構函式:一次是在建立子型別原型的時候,另一次是在子型別建構函式內部。沒錯,子型別最終會包含超型別物件的全部例項屬性,但我們不得不在呼叫子型別建構函式時重寫這些屬性,再來看一下下面的例子:
``` js
function SuperType(name){
this.name = name;
this.colors = ['red','black']
}
SuperType.prototype.sayName = function (){
console.log(this.name)
}
function SubType(name, age){
SuperType.call(this, name) // 第二次呼叫SuperType
this.age = age
}
SubType.prototype = new SuperType() // 第一次呼叫SuperType
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function (){
console.log(this.age)
}
```
第一次呼叫SuperType建構函式時,SubType.prototype會得到兩個屬性:name和colors。他們都是SuperType的例項屬性,只不過現在位於SubType的原型中。當呼叫SubType建構函式時,又會呼叫一次SuperType建構函式,這個一次又在新物件上建立了例項屬性name和colors。於是,這兩個屬性就遮蔽了原型中的兩個同名屬性。即有兩組name和colors屬性:一組在例項上,一組在原型上。這就是呼叫兩次SuperType建構函式的結果。解決這個問題的方法就是————寄生組合式繼承。
所謂寄生組合式繼承,即通過借用建構函式來繼承屬性,通過原型鏈的混成形式來繼承方法。其背後的基本思路是:不必為了制定子型別的原型而呼叫超型別的建構函式,我們所需要的無非就是超型別原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超型別的原型,然後再將結果指定給子型別的原型。寄生組合式繼承的基本模式如下:
``` js
function object(o) {
function F (){}
F.prototype = o;
return new F()
}
function inheritPtototype(subType, superType){
var prototype = object(superType.prototype) // 建立物件
prototype.constructor = subType // 增強物件
subType.prototype = prototype // 指定物件
}
function SuperType(name){
this.name = name
this.colors = ['red', 'white']
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function SubType(name,age){
SuperType.call(this,name)
this.age = age
}
inheritPtototype(SubType, SuperType)
SubType.prototype.sayAge = function(){
console.log(this.age)
}
var instance = new SubType('ccc', 18)
instance.sayName() // --> ccc
instance.sayAge() // --> 18
console.log(instance)
```
控制檯打印出的結構:
![](https://img2020.cnblogs.com/blog/1950998/202007/1950998-20200718170513548-467317780.png)
詳細的圖解:
![](https://img2020.cnblogs.com/blog/1950998/202007/1950998-20200718170646518-547921318.png)
這個例子的高效率提現在它值呼叫了一次SuperType建構函式,並且因此避免了在SubType.prototype上面建立不必要的、多餘的屬性。與此同時,原型鏈還能保持不變;因此,還能夠正常使用instanceof和isPrototypeOf()。這也是很多大廠用的繼承