面向對象的JavaScript 四 ----- Javascript實現繼承的方式(1)


在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"
很不錯吧。

關於繼承還有很多內容可講,我會在之後的文章中一一道來。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章