6 javascript面向對象的程式設計
-
- 6.3.1 原型鍊
- 6.3.2 借用構造函數
- 6.3.3 組合繼承
- 6.3.4 原型式繼承
- 6.3.5 寄生式繼承
- 6.3.6 寄生組合式繼承
複習原型對象:
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
6.3.1 原型鍊
原型鍊:基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
假如我們讓原型對象等于另一個類型的執行個體,原型對象将包含一個指向另一個原型的指針,相應地,另一個原型中也包含着一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的執行個體,那麼上述關系依然成立,如此層層遞進,就構成了實
例與原型的鍊條。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
每個類型分别有一個屬性和一個方法。它們的主要差別是 SubType 繼承了 SuperType,而繼承是通過建立 SuperType 的執行個體,并将該執行個體賦給SubType.prototype 實作的。實作的本質是重寫原型對象,代之以一個新類型的執行個體。
在通過原型鍊實作繼承的情況下,搜尋過程就得以沿着原型鍊繼續向上。就拿上面的例子來說,調用instance.getSuperValue()會經曆三個搜尋步驟: 1)搜尋執行個體; 2)搜尋 SubType.prototype;3)搜尋 SuperType.prototype,最後一步才會找到該方法。
-
預設的原型
所有函數的預設原型都是 Object 的執行個體,是以預設原型都會包含一個内部指針,指向 Object.prototype。這也正是所有自定義類型都會繼承 toString()、valueOf()等預設方法的根本原因。
-
确定原型和執行個體的關系
可以通過兩種方式來确定原型和執行個體之間的關系。第一種方式是使用 instanceof 操作符,隻要用這個操作符來測試執行個體與原型鍊中出現過的構造函數,結果就會傳回 true。第二種方式是使用 isPrototypeOf()方法。同樣,隻要是原型鍊中出現過的原型,都可以說是該原型鍊所派生的執行個體的原型,是以 isPrototypeOf()方法也會傳回 true。
-
謹慎地定義方法
子類型有時候需要重寫超類型中的某個方法,或者需要添加超類型中不存在的某個方法。但不管怎樣,給原型添加方法的代碼一定要放在替換原型的語句之後。
-
原型鍊的問題
最主要的問題來自包含引用類型值的原型。想必大家還記得,我們前面介紹過包含引用類型值的原型屬性會被所有執行個體共享;而這也正是為什麼要在構造函數中,而不是在原型對象中定義屬性的原因。在通過原型來實作繼承時,原型實際上會變成另一個類型的執行個體。于是,原先的執行個體屬性也就順理成章地變成了現在的原型屬性了。
原型鍊的第二個問題是:在建立子類型的執行個體時,不能向超類型的構造函數中傳遞參數。
6.3.2 借用構造函數
借用構造函數(constructor stealing)的技術(有時候也叫做僞造對象或經典繼承)。這種技術的基本思想相當簡單,即在子類型構造函數的内部調用超類型構造函數。
function SubType(){
//繼承了 SuperType
SuperType.call(this);
}
通過使用 apply()和 call()方法也可以在(将來)新建立的對象上執行構造函數
- 傳遞參數
function SubType(){
//繼承了 SuperType,同時還傳遞了參數
SuperType.call(this, "Nicholas");
//執行個體屬性
this.age = 29;
}
2.借用構造函數的問題
如果僅僅是借用構造函數,那麼也将無法避免構造函數模式存在的問題——方法都在構造函數中定義,是以函數複用就無從談起了。而且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結果所有類型都隻能使用構造函數模式。
6.3.3 組合繼承
組合繼承(combination inheritance),有時候也叫做僞經典繼承,指的是将原型鍊和借用構造函數的技術組合到一塊,進而發揮二者之長的一種繼承模式。
6.3.4 原型式繼承
這種方法并沒有使用嚴格意義上的構造函數。他的想法是借助原型可以基于已有的對象建立新對象,同時還不必是以建立自定義類型。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
6.3.5 寄生式繼承
寄生式(parasitic)繼承是與原型式繼承緊密相關的一種思路,并且同樣也是由克羅克福德推而廣之的。寄生式繼承的思路與寄生構造函數和工廠模式類似,即建立一個僅用于封裝繼承過程的函數,該函數在内部以某種方式來增強對象,最後再像真地是它做了所有工作一樣傳回對象。
function createAnother(original){
var clone = object(original); //通過調用函數建立一個新對象
clone.sayHi = function(){ //以某種方式來增強這個對象
alert("hi");
};
return clone; //傳回這個對象
}
使用寄生式繼承來為對象添加函數,會由于不能做到函數複用而降低效率;這一
點與構造函數模式類似。
6.3.6 寄生組合式繼承
組合繼承是 JavaScript 最常用的繼承模式;不過,它也有自己的不足。組合繼承最大的
問題就是無論什麼情況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另一次是在子類型構造函數内部。沒錯,子類型最終會包含超類型對象的全部執行個體屬性,但我們不得不在調用子類型構造函數時重寫這些屬性。
所謂寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鍊的混成形式來繼承方法。其背後的基本思路是:不必為了指定子類型的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然後再将結果指定給子類型的原型。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //建立對象
prototype.constructor = subType; //增強對象
subType.prototype = prototype; //指定對象
}
``