## 6.3 繼承
由于函數沒有簽名,在ECMAScript中無法實現接口繼承,ECMAScript只支持**實現繼承**,而且其實現繼承主要是依靠**原型鏈**來實現的。
### 6.3.1 原型鏈
基本思想:利用原型讓一個引用類型繼承另一個引用類型。
~~~
function SuperType(){
this.property = true;
}
SuperType.property.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType(); //繼承了SuperType
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue) //true
~~~
此時`instance.constructor`現在指向的是`SuperType`,這是因為`SubType`的原型指向了另一個對象`SuperType的原型`,而這個原型對象的constructor屬性指向的是SuperType。
**1. 別忘記默認的原型**
所有函數的默認原型都是Object的實例,因此默認原型都會包含一個**內部指針**,指向`Object.prototype`,這正是所有自定義類型都會繼承toString()、valueOf等默認方法的根本原因。
**2. 確定原型和實例的關系**
通過兩種方式來確定原型與實例之間的關系。第一種方式是使用`instanceof`操作符;第二種方式是使用`isPrototypeOf()`方法。
~~~
alert(SubType.prototype.isPrototypeOf(instance)); //true;
~~~
**3. 謹慎地定義方法**
給原型添加方法的代碼一定要放在**替換**原型的語句**之后**。
提醒:在通過原型鏈實現繼承時,不能使用對象字面量創建原型方法,因為這樣會**重寫原型鏈**。
**4. 原型鏈的問題**
* 包含引用類型值的原型,其屬性會被所有實例共享。
* 在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。
### 6.3.2 借用構造函數(偽構造對象或經典繼承)
**基本思想:在子類型構造函數的內部調用超類型構造函數。**
~~~
function SuperType(){
this.colors = ['red','yellow','blue'];
}
function SubType(){
SuperType.call(this); //繼承SuperType
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
~~~
通過使用`call()或者apply()`方法,我們實際上是在(未來將要)創建的SubType實例的環境下調用了SuperType構造函數。這樣一來,就會在新SubType()對象上執行SuperType()函數中定義的所有對象初始化代碼。結果,SubType的每個實例就都會具有自己的colors屬性的副本。
**1. 傳遞參數**
相對于原型鏈而言,借用構造函數有一個很大的優勢,即**可以在子類型構造函數中向超類型構造函數傳遞參數。**
~~~
function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this,'ken');
this.age = 28;
}
~~~
**2. 借用構造函數的問題**
* 方法都在構造函數中定義,無法函數復用
* 在超類型的原型中定義的方法(使用prototype定義的),對于子類型而言也是不可見的,結果所有類型都只能使用構造函數模式。
### 6.3.3 組合繼承(偽經典繼承)
思路:**使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承**。這樣,既在原型上定義方法實現了函數復用,又能夠保證每個實例都有自己的屬性。
~~~
function SuperType(name){
this.name = name;
this.colors = ['red','blue'.'green];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age) {
//繼承屬性
SuperType.call(this,name);
this.age = age;
}
//繼承方法
SubType.prototype = new SuperType(); //將SuperType的實例復制給SubType的原型
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType('ken',28);
instance1.colors.push('black);
alert(instance1.colors); //'red,blue,green,black'
instance1.sayName(); //'ken'
instance1.sayAge(); //28
var instance2 = new SubType('Greg',27);
alert(instance2.colors); //'red,blue,green'
instance2.sayName(); //'Greg'
instance2.sayAge(); //27
~~~
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了他們的優點,成為JavaScript中最常用的繼承模式。而且,instanceof和isPrototypeOf()也能夠用于識別基于組合繼承創建的對象。
**缺點**:由于多次調用超類型構造函數而導致低效率。
### 6.3.4 原型式繼承
原型式繼承沒有嚴格意義上的使用構造函數,它是借助原型可以基于已有的對象創建新對象,同時還不必因此創建自定義類型。
~~~
function object(o){
function F(){}
F.prototype = o;
return new F();
}
~~~
在object()函數內部,先創建了一個臨時性的構造函數,然后將傳入的對象作為這個構造函數的原型,最后返回這個臨時類型的新實例。從本質上講,object()對傳入其中的對象執行了一次**淺復制**。例:
~~~
var person = {
name: 'Nicholas',
friends: ['wesley','fox']
};
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
alert(person.friends); //'wesley,fox,Rob,Barbie'
~~~
ES5通過新增`Object.create()`方法規范了原型式繼承。這個方法接收兩個參數:一個用作新對象原型的對象和(可選的)一個為新對象定義額外屬性的對象。
注:第二個參數格式與`Object.defineProperties()`方法的第二個參數格式相同:每個屬性都是通過自己的描述符定義的。以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性。
~~~
var person = {
name: 'Nicholas',
friends: ['wesley','fox']
};
var anotherPerson = Object.create(person, {
name:{
value: 'Greg'
}
});
~~~
在沒有必要興師動眾地創建構造函數,而只是想讓一個對象與另個對象保持類似的情況下,原型式繼承是完全可以勝任的。不過別忘了,**包含引用類型值的屬性始終都會共享相應的值,就像使用原型模式一樣。**
### 6.3.5 寄生式繼承
寄生式繼承是與原型式繼承密寫相關的一種思路:**創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象**,最后返回對象。
~~~
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = object(original);
clone.sayHi = function(){
alert('hi');
};
return clone;
}
~~~
object(o)函數不是必須的,任何能夠返回新對象的函數都適用于此模式。
這樣創建的新對象不僅具有original的所有屬性和方法,而且還有自己的sayHi()方法。在主要考慮對象而不是自定義類型和構造函數的情況下,寄生式繼承也是一種有用的模式。
### 6.3.6 寄生組合式繼承(最有效理想)
寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。
思路是:不必為了指定子類型的原型調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。本質上,就是**使用寄生式繼承來繼承超類型的原型,然后再將結果指定給子類型的原型**。
~~~
function inheritPrototype(subType,SuperType){
var prototype = object(subType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name){
this.name = name;
this.colors = ['red','blue'.'green];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age) {
//繼承屬性
SuperType.call(this,name);
this.age = age;
}
//繼承方法
inheritPrototype(subType,SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
}
~~~
寄生組合式繼承與組合繼承的區別在于,**前者不將超類型中的實例屬性繼承為子類型的原型屬性。**
- 前言
- 第一章 JavaScript簡介
- 第三章 基本概念
- 3.1-3.3 語法、關鍵字和變量
- 3.4 數據類型
- 3.5-3.6 操作符、流控制語句(暫略)
- 3.7函數
- 第四章 變量的值、作用域與內存問題
- 第五章 引用類型
- 5.1 Object類型
- 5.2 Array類型
- 5.3 Date類型
- 5.4 基本包裝類型
- 5.5 單體內置對象
- 第六章 面向對象的程序設計
- 6.1 理解對象
- 6.2 創建對象
- 6.3 繼承
- 第七章 函數
- 7.1 函數概述
- 7.2 閉包
- 7.3 私有變量
- 第八章 BOM
- 8.1 window對象
- 8.2 location對象
- 8.3 navigator、screen與history對象
- 第九章 DOM
- 9.1 節點層次
- 9.2 DOM操作技術
- 9.3 DOM擴展
- 9.4 DOM2和DOM3
- 第十章 事件
- 10.1 事件流
- 10.2 事件處理程序
- 10.3 事件對象
- 10.4 事件類型
- 第十一章 JSON
- 11.1-11.2 語法與序列化選項
- 第十二章 正則表達式
- 12.1 創建正則表達式
- 12.2-12.3 模式匹配與RegExp對象
- 第十三章 Ajax
- 13.1 XMLHttpRequest對象
- 你不知道的JavaScript
- 一、作用域與閉包
- 1.1 作用域
- 1.2 詞法作用域
- 1.3 函數作用域與塊作用域
- 1.4 提升
- 1.5 作用域閉包
- 二、this與對象原型
- 2.1 關于this
- 2.2 全面解析this
- 2.3 對象
- 2.4 混合對象“類”
- 2.5 原型
- 2.6 行為委托
- 三、類型與語法
- 3.1 類型
- 3.2 值
- 3.3 原生函數