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

四,封裝繼承
之前的代碼有個很大的缺點,每次繼承的時候,我們都要輸入重複的代碼。所以,我們可以把繼承的代碼獨立開來,代碼如下: function extend(Child, Parent) {  
var F = function(){};  
F.prototype = Parent.prototype;  
Child.prototype = new F();  
Child.prototype.constructor = Child;  
Child.uber = Parent.prototype;
}
使用繼承的時候,只要像這樣:
>>>extend(TwoDShape, Shape);
>>>extend(Triangle, TwoDShape);
這樣繼承使用起來更加便捷了。
其實這就是YUI實現繼承的方式.比如你使用YUI,可以這樣調用:
>>>YAHOO.lang.extend(Triangle, Shape) 

五.拷貝繼承
另外還有一個方法實現繼承,我們可以簡單的賦值屬性,代碼如下:
function extend2(Child, Parent) {
var p = Parent.prototype;  
var c = Child.prototype;  
for (var i in p) {
     c[i] = p[i];  
}  
c.uber = p;
}
看出代碼的特點了吧,這種實現繼承的方式只是簡單的拷貝屬性,所以效率要比之前的繼承方法差。但是拷貝繼承後,繼承的對象有屬性的實例(其實拷貝的是引用)。我們寫段代碼解釋下:
>>> var A = function(){}, B = function(){};
>>> A.prototype.stuff = [1,2,3];       
        [1, 2, 3]
>>> A.prototype.name = 'a';
        "a"
>>> extend2(B, A);
>>> B.prototype.hasOwnProperty('name')
      true
>>> B.prototype.hasOwnProperty('stuff')
      true
>>> B.prototype.stuff
     [1, 2, 3]
>>> B.prototype.stuff === A.prototype.stuff (注意,這裏是全等號)
 true
修改b不影響A
>>> B.prototype.name += 'b'       
      "ab" 
>>> A.prototype.name
        "a"
 但是修改stuff回影響A
>>> B.prototype.stuff.push(4,5,6);
>>> A.prototype.stuff
        [1, 2, 3, 4, 5, 6]
看到區別了吧,如果是array等對象,實質拷貝的是引用,所以修改B的Array會同樣影響A中的Array.而name是基本類型,所以不會受到影響.
另外,在提供一個不是用構造函數的繼承方法,代碼如下:
function extendCopy(p) {
  var c = {}; 
  for (var i in p) {
    c[i] = p[i];
  }
  c.uber = p;
  return c;
}
很容易理解,返回一個對象,其屬性已經將父類的屬性賦值給新對象了.
早期的jQuery和Prototype就是這樣實現繼承的.

這些都是淺拷貝實現繼承的方法,那麼深拷貝呢?代碼如下:
function deepCopy(p, c) {
  var c = c || {};
  for (var i in p) {
    if (typeof p[i] === 'object') {
      c[i] = (p[i].constructor === Array) ? [] : {}; [] : {};
      deepCopy(p[i], c[i]);
    } else {
      c[i] = p[i]; c[i] = p[i];
    }
  }
  return c;
}
看懂了嗎?其實就是,如果發現某個屬性是對象,就遞歸調用來拷貝這個屬性裏的每一個子屬性.這樣就彌補了之前淺拷貝遇到的問題了.

六 多重繼承
雖然多重繼承並不是一個好的設計架構,但是我們還是要實現一下,畢竟其他面向對象的語言也實現了多重繼承.
function multi() {
  var n = {}, stuff, j = 0, len = arguments.length;
  for (j = 0; j < len; j++) {
    stuff = arguments[j];
    for (var i in stuff) {
      n[i] = stuff[i];
    }
  }
  return n;
}
其實代碼很簡單,只是將繼承的父類作爲參數傳入,然後一一拷貝即可以了.

七 寄生繼承
這個名字不是很好聽,但還是很形象的,代碼如下:
function object(o) {
  var n;
  function F() {}
  F.prototype = o;
  n = new F();
  n.uber = o;
  return n;
}

var twoD = {
  name: '2D shape',
  dimensions: 2
};
function triangle(s, h) {
  var that = object(twoD);//注意這裏
  that.name ='Triangle';
  that.getArea = function(){return this.side * this.height / 2;};
  that.side = s;
  that.height = h;
  return that;
}

var t = triangle(5, 10);
>>> t.dimensions
確實很像寄生,子類包含了一個that屬性,該屬性繼承了父類.

八,借用構造函數實現繼承
你也許認爲實現繼承的方式太多了,最後在介紹一種繼承(我保證是最後一種了).代碼如下:
function Shape(id) {
  this.id = id;
}
Shape.prototype.name = 'shape';
Shape.prototype.toString = function(){return this.name;};
function Triangle() {
    Shape.apply(this, arguments);
}
Triangle.prototype.name = 'Triangle';
看懂了嗎,只是簡單的再次調用父類的構造函數,只是傳入的參數不同.是不是有點投機取巧的感覺.

本文暫寫到這裏,之後會對這些繼承的方式做個總結.


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