JavaScript繼承基礎講解(原型鏈、借用構造函數、混合模式、原型式繼承、寄生式繼承、寄生組合式繼承)
說好的講解JavaScript繼承,可是遲遲到現在講解。廢話不多說,直接進入正題。
既然你想了解繼承,證明你對JavaScript面向對象已經有一定的了解,如還有什么不理解的可以參考《面向對象JS基礎講解,工廠模式、構造函數模式、原型模式、混合模式、動態(tài)原型模式》,接下來講一般通過那些方法完成JavaScript的繼承。
原型鏈
JavaScript中實現繼承最簡單的方式就是使用原型鏈,將子類型的原型指向父類型的實例即可,即“子類型.prototype = new 父類型();”,實現方法如下:
// 為父類型創(chuàng)建構造函數
function SuperType() {
this.name = ['wuyuchang', 'Jack', 'Tim'];
this.property = true;
}
// 為父類型添加方法
SuperType.prototype.getSuerperValue = function() {
return this.property;
}
// 為子類型創(chuàng)建構造函數
function SubType() {
this.test = ['h1', 'h2', 'h3', 'h4'];
this.subproperty = false;
}
// 實現繼承的關鍵步驟,子類型的原型指向父類型的實例
SubType.prototype = new SuperType();
// 在此處給子類型添加方法,一定要在實現繼承之后,否則會在將指針指向父類型的實例,則方法為空
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
/* 以下為測試代碼示例 */
var instance1 = new SubType();
instance1.name.push('wyc');
instance1.test.push('h5');
alert(instance1.getSuerperValue()); // true
alert(instance1.getSubValue()); // false
alert(instance1.name); // wuyuchang,Jack,Tim,wyc
alert(instance1.test); // h1,h2,h3,h4,h5
var instance2 = new SubType();
alert(instance2.name); // wuyuchang,Jack,Tim,wyc
alert(instance2.test); // h1,h2,h3,h4
可以看到如上的代碼就是通過原型鏈實現的一個簡單的繼承,但看到測試代碼示例中還是存在些問題。相信看了我的博文《面向對象JS基礎講解,工廠模式、構造函數模式、原型模式、混合模式、動態(tài)原型模式》的童鞋一定知道原型鏈代碼存在的第一個問題是由于子類型的原型是父類型的實例,也就是子類型的原型中包含的父類型的屬性,從而導致引用類型值的原型屬性會被所有實例所共享。以上代碼的instance1.name.push('wyc');就可以證明此問題的存在。而原型鏈的第二個問題就是:在創(chuàng)建子類型的實例時,不能向超類型的構造函數中傳遞參數。因此我們在實際開發(fā)中,很少單獨使用原型鏈。
借用構造函數
為了解決原型鏈中存在的兩個問題,開發(fā)人員開始使用一種叫做借用構造函數的技術來解決原型鏈中存在的問題。這種技術的實現思路也挺簡單,只需要在子類型的構造函數內調用父類型的構造函數即可。別忘了,函數只不過是在特定環(huán)境中執(zhí)行代碼的對象,因此可以通過apply()或call()方法執(zhí)行構造函數。代碼如下:
// 為父類型創(chuàng)建構造函數
function SuperType(name) {
this.name = name;
this.color = ['pink', 'yellow'];
this.property = true;
this.testFun = function() {
alert('http://tools.jb51.net/');
}
}
// 為父類型添加方法
SuperType.prototype.getSuerperValue = function() {
return this.property;
}
// 為子類型創(chuàng)建構造函數
function SubType(name) {
SuperType.call(this, name);
this.test = ['h1', 'h2', 'h3', 'h4'];
this.subproperty = false;
}
// 在此處給子類型添加方法,一定要在實現繼承之后,否則會在將指針指向父類型的實例,則方法為空
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
/* 以下為測試代碼示例 */
var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']);
instance1.name.push('hello');
instance1.test.push('h5');
instance1.color.push('blue');
instance1.testFun(); // http://tools.jb51.net/
alert(instance1.name); // wuyuchang,Jack,Nick,hello
// alert(instance1.getSuerperValue()); // error 報錯
alert(instance1.test); // h1,h2,h3,h4,h5
alert(instance1.getSubValue()); // false
alert(instance1.color); // pink,yellow,blue
var instance2 = new SubType('wyc');
instance2.testFun(); // http://tools.jb51.net/
alert(instance2.name); // wyc
// alert(instance2.getSuerperValue()); // error 報錯
alert(instance2.test); // h1,h2,h3,h4
alert(instance2.getSubValue()); // false
alert(instance2.color); // pink,yellow
可以看到以上代碼中子類型SubType的構造函數內通過調用父類型"SuperType.call(this, name);",從而實現了屬性的繼承,也可以在子類型創(chuàng)建實例的時候為父類型傳遞參數了,但新的問題又來了??梢钥吹轿以诟割愋偷臉嬙旌瘮抵卸x了一個方法:testFun,在父類型的原型中定義了一個方法:getSuperValue??墒窃?span style="color: #ff0000">實例化子類型后仍然是無法調用父類型的原型中定義的方法getSuperValue,只能調用父類型中構造函數的方法:testFun。這就同創(chuàng)建對象中只使用構造函數模式一樣,使得函數沒有復用性可言??紤]到這些問題,借用構造函數的技術也是很少單獨使用的。
組合繼承(原型鏈+借用構造函數)
顧名思義,組合繼承就是結合使用原型鏈與借用構造函數的優(yōu)點,組合而成的一個模式。實現也很簡單,既然是結合,那當然結合了兩方的優(yōu)點,即原型鏈繼承方法,而在構造函數繼承屬性。具體代碼實現如下:
// 為父類型創(chuàng)建構造函數
function SuperType(name) {
this.name = name;
this.color = ['pink', 'yellow'];
this.property = true;
this.testFun = function() {
alert('http://tools.jb51.net/');
}
}
// 為父類型添加方法
SuperType.prototype.getSuerperValue = function() {
return this.property;
}
// 為子類型創(chuàng)建構造函數
function SubType(name) {
SuperType.call(this, name);
this.test = ['h1', 'h2', 'h3', 'h4'];
this.subproperty = false;
}
SubType.prototype = new SuperType();
// 在此處給子類型添加方法,一定要在實現繼承之后,否則會在將指針指向父類型的實例,則方法為空
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
/* 以下為測試代碼示例 */
var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']);
instance1.name.push('hello');
instance1.test.push('h5');
instance1.color.push('blue');
instance1.testFun(); // http://tools.jb51.net/
alert(instance1.name); // wuyuchang,Jack,Nick,hello
alert(instance1.getSuerperValue()); // true
alert(instance1.test); // h1,h2,h3,h4,h5
alert(instance1.getSubValue()); // false
alert(instance1.color); // pink,yellow,blue
var instance2 = new SubType('wyc');
instance2.testFun(); // http://tools.jb51.net/
alert(instance2.name); // wyc
alert(instance2.getSuerperValue()); // true
alert(instance2.test); // h1,h2,h3,h4
alert(instance2.getSubValue()); // false
alert(instance2.color); // pink,yellow
以上代碼通過SuperType.call(this, name);繼承父類型的屬性,通過SubType.prototype = new SuperType();繼承父類型的方法。以上代碼很方便的解決了原型鏈與借用構造函數所遇到的問題,成為了JavaScript中最為常用的實例繼承的方法。但混合模式也并非沒有缺點,可以看到在以上代碼中在繼承方法的時候實際已經繼承了父類型的屬性,只不過此時對于引用類型屬于共享的,因此在子類型的構造函數內在次調用父類型的構造函數從而繼承了父類型的屬性而去覆蓋了原型中所繼承的屬性,這樣調用兩次構造函數顯然沒有必要,但有什么方法可以解決呢?在解決此問題時先看以下兩個模式。
原型式繼承
原型式繼承的的實現方法與普通繼承的實現方法不同,原型式繼承并沒有使用嚴格意義上的構造函數,而是借助原型可以基于已有的對象創(chuàng)建新對象,同時還不必因此創(chuàng)建自定義類型。具體代碼如下:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
代碼示例:
/* 原型式繼承 */
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = {
name : 'wuyuchang',
friends : ['wyc', 'Nicholas', 'Tim']
}
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Bob');
var anotherPerson2 = object(person);
anotherPerson2.name = 'Jack';
anotherPerson2.friends.push('Rose');
alert(person.friends); // wyc,Nicholas,Tim,Bob,Rose
寄生式繼承
/* 寄生式繼承 */
function createAnother(original) {
var clone = object(original);
clone.sayHi = function() {
alert('hi');
}
return clone;
}
使用示例:
/* 原型式繼承 */
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;
}
var person = {
name : 'wuyuchang',
friends : ['wyc', 'Nicholas', 'Rose']
}
var anotherPerson = createAnother(person);
anotherPerson.sayHi();
寄生組合式繼承
前面說過了JavaScrip中組合模式實現繼承的缺點,現在我們就來解決它的缺點,實現思路是,對于構造函數繼承屬性,而原型鏈的混成形式繼承方法,即不用在繼承方法的時候實例化父類型的構造函數。代碼如下:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
/* 寄生組合式繼承 */
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
而在使用時只需要將組合模式中的“SubType.prototype = new SuperType();”這行代碼替換成inheritPrototype(subType, superType);即可。寄生組合式繼承的高效率體現在它只調用了一次父類型構造函數,避免了創(chuàng)建不必要的或多余的屬性。與此同時,原型鏈還能保持不變,因此,還能夠正常使用instanceof和isPrototypeof()。這也是目前來說最理想的繼承方式了,目前也在向這種模式轉型。(YUI也使用了這種模式。)
此博文參考《JavaScript高級程序設計第3版》,代碼為經過改寫,更具體,并加了注釋使大家更易懂。如對JS繼承方面有獨到見解的童鞋不別吝嗇,回復您的見解供大家參考!

