[toc]
JavaScript中的每個類都有三個部分內容:
- 構造函數內部提供實例化對象復制用
- 構造函數外部通過.語法添加的
- 原型中的
面向對象程序設計中有繼承的概念,而在JavaScript中繼承是通過原型鏈實現的。
實現繼承的方式有如下幾種:
- 類式繼承
- 構造函數繼承
- 組合繼承
- 原型式繼承
- 寄生式繼承
- 寄生組合式繼承
### 1. 類式繼承
類式繼承是最簡單的一種繼承實現方式。
```js
//父類構造函數
function SuperClass(){
this.superValue = true;
}
//父類方法
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
}
//子類構造函數
function SubClass(){
this.subValue = false;
}
//繼承父類
SubClass.prototype = new SuperClass();
//子類方法
SubClass.protorype.getSubValue = function(){
return this.subValue;
}
```
類式繼承通過將父類的實例賦值給子類的構造函數的原型(prototype屬性),類的原型對象就是為了為類的原型添加共有方法,但是類不能直接訪問這些屬性和方法,只能通過原型prototype來訪問,在實例化父類時,新創建的對象復制了父類構造函數內的屬性和方法并將其原型__proto__指向了父類的原型對象。而將這個新創建的對象賦值給子類的原型,則子類的原型就可以訪問到父類的原型屬性和方法。
所以子類不僅可以訪問父類構造函數中的屬性和方法,還可以訪問父類原型上的屬性和方法。
類式繼承有兩個缺點:
- 子類通過prototype對父類進行實例化,所以如果父類中的屬性是引用類型時,某個子類對這個屬性進行更改,會影響到其他的子類
- 子類的實現是通過原型pototype對父類進行實例化實現的,在創建父類時,無法向父類傳遞參數,因而在實例化父類時也無法對父類構造函數內的屬性進行初始化
### 2. 構造函數繼承
```js
//父類構造函數
function SuperClass(id){
this.books = ["c","c++","java"];
this.id = id;
}
//父類原型方法
SuperClass.prototype.showBooks = function(){
console.log(this.books);
}
//子類構造函數
function SubClass(id){
SuperClass.call(this,id);
}
```
SuperClass.call(this,id)是構造函數繼承的精華,call方法可以改變函數的作用環境,因此在子類中可以將子類中的變量在父類的構造函數中執行一遍,在父類中通過this綁定屬性,因此子類也就繼承了父類的共有屬性。要注意子類可繼承的屬性和方法僅僅是構造函數中的屬性和方法。
構造函數繼承沒有涉及到prototype,所以父類的原型方法不會被子類繼承,如果將屬性或方法放在構造函數中來實現繼承,那么每個子類都會有一個單獨的屬性或方法,不符合代碼服用的原則。
### 3. 組合繼承
類式繼承和構造函數繼承各有利弊,如果將兩種繼承方式結合,則就成了組合繼承。
```js
//父類構造函數
function SuperClass(name){
this.name = name;
this.books = ["c","c++","java"];
}
//父類原型方法
SuperClass.prototype.getName = function(){
return this.name;
}
//子類構造函數
function SubClass(name,time){
SuperClass.call(this,name); //構造函數繼承
this.time = time;
}
//類式繼承
SubClass.prototype = new SuperClass();
//子類方法
SubClass.prototype.getTime = function(){
return this.time;
}
```
組合繼承融合了類式繼承和構造函數繼承的優點,但是也有一個明顯的缺點,就是在實例化子類時父類的構造函數會執行兩次。
### 4. 原型式繼承
原型式繼承的觀點是可以借助prototype根據已有的對象創建一個新對象,而不必同時穿件新的自定義對象類型。
```js
//原型式繼承
function inheritObject(o){
function F(){}; //聲明一個過渡函數對象
F.prototype = o; //繼承父類
return new F(); //返回過渡對象的一個實例
}
```
這種繼承是類式繼承的一個封裝,其中的過渡對象相當于類式繼承中的子類,由于過渡類的構造函數中無內容,因此開銷比較小。
原型繼承的缺點與類式繼承一樣,父類的引用類型屬性也會被公用。
### 5. 寄生式繼承
```js
//父類或基對象
var book = {
name:"js book",
alikeBook:["css","html"]
}
//繼承并返回子類
function createBook(obj){
var o = new inheritObject(obj);
o.getName = function(){
return this.name;
}
return o;
}
var book1 = createBook(book); //創建子類,以book為父類
```
寄生式繼承就是對原型繼承進行了二次封裝,在封裝過程中可以對繼承的對象進行拓展,可以為子類添加新的屬性和方法。
### 6. 寄生組合式繼承
在將類式繼承與構造函數繼承組合使用時有一個問題就是子類不是父類的實例,而子類的原型是父類的實例,而寄生式組合繼承則將寄生式繼承以及構造函數繼承組合使用。
```js
//寄生組合式繼承
function inheritPrototype(subClass,superClass){
//復制父類的原型的副本
var p = inheritObject(superClass.prototype);
p.constructor = subClass; //將副本的構造函數指向子類的構造函數
subClass.prototype = p; //設置子類的原型
}
```
寄生組合式繼承中,修改了父類的原型副本的構造函數為子類的構造函數,但是保留了副本的其他屬性,這是因為我們需要的是父類的原型,而不再需要調用父類的構造函數。而父類的構造函數在inheritObject中已經調用,屬性和方法已經被初始化。
### 多繼承和多態
#### 多繼承
JavaScript的繼承基于原型鏈,但是只有一條原型鏈,因此JavaScript中的多繼承要通過其他方法實現。
```js
function extent(target,source){
for(var property in source){
target[property] = source[property];
}
return target;
}
```
多繼承就是一個屬性的復制過程,但是這個復制是淺復制,只能復制值類型的屬性,而不能復制引用類型的屬性。
當然也可以同時對多個對象進行集成,只需要將上述方法的參數設為不固定,在函數內部判斷參數個數并添加一層循環依次復制即可。
#### 多態
多態就是一種方法的不同調用方式,在JavaScript中多態的實現是通過對參數進行判斷,執行不同的操作實現的。