1. 程式人生 > 其它 >《JavaScript設計模式》章4--繼承

《JavaScript設計模式》章4--繼承

類式繼承

原型鏈

  1. 首先我們建立一個建構函式

建立例項屬性要使用關鍵字this,類的方法應該被新增到其prototype物件中,通過new關鍵字可以建立該類的例項物件

/* Class Person.  下面這段程式碼是照著原文手打的*/
function Person(name) {  //類名大寫
  this.name = name;   //類的例項屬性使用this
}

Person.prototype.getName = function() { //類的方法新增到prototype物件中
  return this.name
}
  1. 建立一個繼承Person的類

繼承一個類比建立一個類要複雜很多,java中只需要一個extend關鍵字即可

js中沒有extend關鍵字,每個物件都有一個prototype屬性,這個屬性要麼指向一個物件,要麼為null
在訪問物件的某個成員時,如果該成員為見於當前物件,則會在該物件的prototype屬性所指物件(其原型物件)中查詢,沿著原型鏈以此類推,直到找到為止。
這意味著,在js中,一個類需要繼承一個類的時候,只需要將子類的prototype屬性指向超類的一個例項即可

/* Class Author.     下面這段程式碼是照著原文手打的*/
function Author(name, books){
  Person.call(this, name)
  this.books = books
}
function.prototype = new Person()
Author.prototype.constructor = Author
Author.prototype.getBooks = function() {
  return this.books
}

上面這段程式碼解釋一下:

首先,像Person一樣建立一個建構函式,在建構函式中呼叫超類的建構函式;
然後,設定原型鏈
最後,重新執行constructor,由於在指定prototype為Person時,其constructor屬性被抹除了

extend函式

為了簡化類式繼承,我們將上面的過程封裝成一個函式extend

function extend(subClass, superClass) {
  /*
    F是一個空函式, 這裡用它建立了一個物件例項插入到原型鏈中,避免建立超類的新例項
    原因是,超累的建構函式可能有一些副作用,可能比較龐大,或者會執行一些大量的計算
  */
  var F = function() {}
  F.prototype = superClass.prototype
  subClass.prototype = new F()
  subClass.prototype.constructor = subClass
} 

使用extend函式來重寫之前的類式繼承

/*extend function*/
function Person(name) {
  this.name = name  
}
Person.prototype.getName = function() {
  return this.name
}
function Author(name, books) {
  Person.call(this, name)
  this.books = books
}
extend(Author, Person)
Author.prototype.getBooks = function() {
  return this.books
}

可以看到,不用手動設定prototype和constructor屬性,而是在宣告類之後,在向子類的prototype物件中新增方法之前使用extend實現繼承
但是,有個問題, 在宣告Author類時,超類名Person被固化了

改進一下extend函式

  /*extend function.  improved.*/
  function extend(subClass, superClass) {
    var F = function () {};
    F.prototype = superClass.prototype;

    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;
    
    /*這小段程式碼我看不懂。。
      看懂了。。回來補上我又
      這裡的superclass是小寫,是新增的一個屬性,意思就是子類的superclass屬性指向了超類的prototype物件*/
    subClass.superclass = superClass.prototype;
    if(superClass.prototype.constructor == Object.prototype.constructor) {
      superClass.prototype.constructor = superClass;
    }
  }

使用進階版的extend函式

function Author(name, books) {
  /*
    這裡的Author.superclass.constructor = Person.prototype.constructor,其實就是Person...
    所以這一句其實就跟之前的Person.call(this, name)一樣。。。
  */

  Author.superclass.constructor.call(this, name) 
  this.books = books
}
extend(Author, Person)
Author.prototype.getBooks = function() {
  return this.books
}

有點長,喝口水

原型式繼承

首先, 用一個類的宣告定義物件的結構
然後,例項化該類來建立一個新物件,

用這種方式建立的物件都有一套該類所有例項屬性的副本,每個例項方法只存在一份,但每個副本擁有一個指向它的連結

/*Person prototype Object*/
var Person = {
  name: 'default name',
  getName: function() {
    return this.name
  }
}

var reader = clone(Person)
reader.getName()  //'default name'
reader.name = 'xxx'
reader.getName()  //'xxx'
/*Author prototype Object*/
var Author  = clone(Person)
Author.books = [] //default value
Author.getBooks = function() {
  return this.books
}


//建立Author例項
var author = clone(Author)
author.name= 'yancy'
/*如果在未給author.books定義新的初始值的情況下,執行author.push,則會影響Author.books,也就影響到了其他還未初始化books的例項物件*/
author.books = ['book1', 'book2'] //初始化books為新的陣列並放入兩個元素

author.getName()
author.getBooks()

clone函式

/*clone function .*/
function clone(object) {
  function F() {}
  F.prototype = object;
  return new F;
}

首先建立了一個空函式F,然後將F的原型物件指向object

類式繼承和原型式繼承對比

兩種繼承範型都可互換使用,類式繼承更符合大眾規範,原型式繼承更符合js特性,更為簡潔。

摻元類

如果多個類中包含同一個函式,這些類邏輯上也不存在任何的繼承關係,那麼可以單獨定義一個摻元類(mixin class)。然後通過某種手段把這個類中的方法擴充到其他需要用到這些方法的類。

/*Mixin class*/
var Mixin =  function() {}
Mixin.prototype = {
  serialize: function() {
    var output = []
    for(key in this) {
      output.push(key+': '+this[key])
    }
    return output.join(',')
  }
}

上面的程式碼聲明瞭一個Mixin類,它有一個serialize方法,例如我們現在需要讓Author類能夠使用該方法。繼承?但是它們邏輯上不存在任何的繼承關係,況且沒有這個必要去繼承。

使用augment函式

augment(Author, Mixin)

var author = new Author('yancy', ['book1', 'book2'])
//現在author作為Author的例項物件,可以直接使用serialize方法
var serializedString = author.serialize()

瞭解一下augment函式

原理很簡單,使用for...in遍歷予類的prototype中的每一個成員,如果受類中不存在該成員,則新增到受類,存在則跳過繼續處理下一個。程式碼如下:

/*Augment function */
function augment(receivingClass, givingClass) {
  for(methodName in givingClass.prototype) {
    if(!receivingClass.prototype[methodName]) {
      receivingClass.prototype[methodName] = givingClass.prototype[methodName]
    }
  }
}

上面的程式碼只是示例,具體可以根據實際情況改進,比如可以讓予類方法直接覆蓋受類方法等。