在javascript中,實現繼承的方式很多,有12種。本文將會對此作一些介紹。
一,初入繼承
首先來看一段代碼
function Shape(){
this.name = 'shape';
this.toString = function() {return this.name;};
}
function TwoDShape(){
this.name = '2D shape';
}
function Triangle(side, height) {
this.name = 'Triangle';
this.side = side;
this.height = height;
this.getArea = function(){return this.side * this.height / 2;};
}
TwoDShape.prototype = new Shape();
Triangle.prototype = new TwoDShape();
注意你需要重設構造函數.(這個在上文已經提到了)
TwoDShape.prototype.constructor = TwoDShape;
Triangle.prototype.constructor = Triangle;
接下來寫點代碼測試一下效果:
>>> var my = new Triangle(5, 10);
>>> my.toString()
"Triangle"
看上去結果不錯,toString()返回了“Triangle”。
這裏我要提一下javascript engine在尋找這個toString函數的步驟:
1,首先會遍歷所有當前對象的屬性,沒有找到toString函數
2。然後將會尋找當前對象的__proto__指向的對象,即TwoDShape對象
3,遍歷TwoDShape對象的所有屬性,仍舊沒找到,然後再查找Shape對象
4,遍歷Shape對象的所有屬性,終於找到了toString函數。因爲調用toString函數的上下文環境是Triangle,所以返回的是Triangle的name屬性。
然後我們在試驗一下instanceof
>>> my instanceof Shape
true
>>> my instanceof TwoDShape
true
>>> my instanceof Triangle
true
>>> my instanceof Array
false
一切正常,然後再試試isPrototypeOf
>>> Shape.prototype.isPrototypeOf(my)
true
>>> TwoDShape.prototype.isPrototypeOf(my)
true
>>> Triangle.prototype.isPrototypeOf(my)
true
>>> String.prototype.isPrototypeOf(my)
false
一切OK。
二,將共享的屬性放在prototype中
看來我們的繼承實現的不錯。但是還是有需要改進的地方。
在介紹下面的內容之前,首先說一下prototype屬性的特點。先看一段代碼
function Shape(){this.name = 'shape';
}
這樣,name存在每個shape實例中.
function Shape(){}
Shape.prototype.name = 'shape';
這樣,所有shape對象共享一個name屬性,具體內容在之前的文章已經介紹了。
之前說到改進,在繼承的時候,沒必要繼承所有的屬性,最好只繼承prototype的屬性,所以修改一下繼承的實現:
function Shape(){}
Shape.prototype.name = 'shape';
Shape.prototype.toString = function() {return this.name;};
function TwoDShape(){}
TwoDShape.prototype = Shape.prototype;
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height) {
this.side = side;
this.height = height;
}
Triangle.prototype = TwoDShape.prototype;
Triangle.prototype.constructor = Triangle;
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function(){return this.side * this.height ;}
注意,現在找尋toString的流程發生了變化,只有兩步了,首先找到當前對象的屬性,然後找prototypr指向對象的屬性,因爲prototype是相等的。注意這裏給prototype賦值得代碼和之前不同
之前:TwoDShape.prototype = new Shape();
現在:Triangle.prototype = TwoDShape.prototype;
你會指出這樣簡單拷貝prototype是不正確的,是的,這樣確實會帶來副作用,比如如下代碼:
>>> var s = new Shape()>>> s.name
"Triangle"
實際上應該返回的是shape,解決方法是:
function Shape(){}
Shape.prototype.name = 'shape';
Shape.prototype.toString = function() {return this.name;};
function TwoDShape(){}
var F = function(){};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height) {
this.side = side;
this.height = height;
}
var F = function(){};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function(){return this.side * this.height };
現在好了,試試:
>>> var my = new Triangle(5, 10);
>>> my.getArea()
25
>>> my.toString()
"Triangle"
在試驗一下繼承:
>> my.__proto__.__proto__.__proto__.constructor
Shape()
>>> var s = new Shape();
>>> s.name
"shape"
這樣就沒問題了。如果你不理解F.prototype = TwoDShape.prototype;Triangle.prototype = new F();和Triangle.prototype = TwoDShape.prototype的區別, 回去找本javascript的語法書翻一下。如果你翻的書上沒講這個內容,那你可以把那本書扔了,告訴你的朋友千萬別在讀那本書了。
三,訪問子類的父類
如果我們需要象java裏的super一樣訪問父類的方法時,該怎麼辦?我的解決方法是,定義一個uber屬性
Tips:爲什麼不用super?很簡單,因爲super是保留字
廢話少說,代碼如下:
function Shape(){}
Shape.prototype.name = 'shape';
Shape.prototype.toString = function(){
var result = [];
if (this.constructor.uber) {
result[result.length] = this.constructor.uber.toString();
}
result[result.length] = this.name;
return result.join(', ');
};
function TwoDShape(){}
var F = function(){};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.uber = Shape.prototype;
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height) {
this.side = side;
this.height = height;
}
var F = function(){};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.uber = TwoDShape.prototype;
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function(){return this.side * this.height ;}
注意,還修改了toString的實現:,現在調用的效果如下>>> var my = new Triangle(5, 10);
>>> my.toString()
"shape, 2D shape, Triangle"
很不錯吧。
關於繼承還有很多內容可講,我會在之後的文章中一一道來。