iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 25
0
自我挑戰組

寇丁人妻的前端書蟲日誌系列 第 25

Day25:JavaScript 設計模式 優良部份 Chapter05 繼承 筆記精要

  • 繼承:
    • 繼承是原始碼再利用的一種形式。
    • 繼承包含型別系統的詳細規則。

擬類別的繼承模式

  • JavaScript 並非讓物件直接繼承物件,而是建構式產生。
  • prototype 是存放存放繼承特質的地方。
  • 我們可以定義建構式,並擴展其 prototype。
  • 建構式如果沒有加上 new ,this 就會被綁到全域變數上。

優雅繼承原型的方法

建立前

建立一個原型及擴展其 prototype

var Mammal = function (name) {
    this.name = name;
}
Mammal.prototype.get_name = function ( ){
    return this.name;
}
Mammal.prototype.says = function ( ){
    return this.saying || '';
}

建立實例:

var myMammal = new Mammal('Herb the Mammal');
var name = myMammal.get_name( ); // Herb the Mammal

建立一個繼承 Mammal 的擬類別:

var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
}
Cat.prototype = new Mammal( );
Cat.prototype.getName = function (){
    return this.says( ) + '' + this.name + ' ' + this.says( );
};

var myCat = new Cat('Henrietta');
var says = myCat.says( );//'meow'
var name = myCat.get_name( );
// 'meow Henrietta meow'

這樣繼承原型步驟繁瑣。

建立後

定義一個繼承方法,可以隱藏醜陋的地方:

Function.method('inherites', function (Parent) {
    this.prototype = new Parent( );
    return this;
})

使用 inherites 方法與 method 方法均回傳 this,再利用 cascade 風格,打造簡潔的程式碼:

var Cat = function (name){
    this.name = name;
    this.saying = 'meow';    
}.inherites(Mammal).
    method('get_name', function( ) {
    return this.says( ) + '' + this.name + ' ' + this.says( );
    });

物件規格器繼承模式

有時候會在建構式增加許多參數,有時候引數順序不太會被記得:

var myObject = maker(f, l, m, c, s);

我們可以寫一個接受單一物件的接收器:

var myObject = maker({
    first: f,
    last: l,
    state: s,
    city: c
});

原型繼承模式

將物件繼承到另外一個物件:

var Mammal = function (name) {
    this.name = name;
}
Mammal.prototype.get_name = function ( ){
    return this.name;
};
Mammal.prototype.says = function ( ){
    return this.saying || '';
};

var myCat = Object.beget(myMammal);

myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.get_name = function ( ) {
    return this.says + ' ' + this.name + ' ' + this.says;
}

像是這樣物件繼承了另一個物件,但新物件又被賦予與原型相同的特性,這樣叫做差別繼承。

函式繼承模式

目前看到的繼承模式都有一個缺點,就是沒有私有成員。

var Mammal = function (spec) {
    var that = {};
    that.get_name = function ( ){
        return spec.name;
    }
    that.says = function ( ){
        return spec.saying || '';
    }
    return that;
}
var myMammal = mammal({name: 'Herb'});

在由建構式 Mammal 組成實例時,我們傳進 spec 物件,讓建構式中只有特定方法(get_name 和 says)才能取得私有變數(saying 和 name)。

之前在擬類別模式中,實例建立之後,還需要重複建構式的工作(差異繼承),但藉由函式繼承模式就不必這樣,僅需要呼叫建構式,讓建構式負責設定的工作。

var cat = function (spec) {
    spec.saying = spec.saying || 'meow';
    var that = mammal(spec);
    that.get_name = function ( ) {
        return that.says( ) + ' ' + spec.name + ' ' + that.says( );
    }
    return that;
}

這個模式也有處理 super 方法的方式,所以我們來製作一個 superior 方法,即使特性更改了,也會呼叫父層的方法:

Object.method('superior', function (name) {
   var that = this;
       method = that[name];
   return function ( ) {
       return method .apply(that, arguments);
   };
});

接下來我們來命名一個 super_get_name 方法,把呼叫 superior 的結果指派給他:

var coolcat = function (spec) {
    var that = cat(spec);
        super_get_name = that.superior('get_name');
    that.get_name = function (n){
        return 'like' + super_get_name( ) + 'baby';
    };
};

var myCoolCat = coolcat({name: 'Bix'});
var name = myCoolCat.get_name( );
// 'like meow Bix meow Baby'

這個模式做到很好的隱藏資訊及取用 super 方法。

零件繼承模式

我們現在來做一個簡易事件處理功能的函式:

 var enentuality = function (that){
      vat registry = {};
      that.fire = function (event){
        // 對物件發出事件,事件是可以包含事件名稱的字串,或是一個物件,
        // 該物件裡包含的 type 特性,保存了事件名稱。
        // 處裡器由 on 方法登記,
        // 其中符合事件名稱的處裡器將被呼叫
        
        var array,
            func,
            handler,
            i,
            type = typeof event === 'string' ? event : event.type; 
      
      // 如果事件有個處裡器陣列,則以迴圈方式處裡陣列,並依序執行處裡器。
      
         if (registry.hasOwnPreoperty(type)){
             array = registry[type];
             for (i = 0; i < array.length; i += 1){
                  handler = array[i];
       // 處裡器的紀錄,包含一個方法與一個選用參數陣列,
       // 如果方法是個名稱,則尋找符合該名稱的函式           
                  func = handler.hethod;
                  if (typeof func === 'string'){
                     func = this[func];
                  }
       // 呼叫處裡器。如果紀錄包含參數,則傳遞參數,否則傳遞 event 物件。
                  func.apply(this,handler.paramerers || [event]);           
                  
             }
         }
        return this;
      };
      that.on = function (type,method,paramerers){
        // 登記一個事件(event)。製作處裡器紀錄。
        // 放入處裡器陣列,如果類型事件尚未存在,則製造一個。
        var handler = {
            method : method,
            paramerers : paramerers
        };
        if (registry.hasOwnPreoperty(type)){
            registry[type].push(handler);
        } else {
            registry[type] = [handler];
        } 
        return this;
      };
      return that;
  };

eventuality(that);

我們可以在任何物件裡呼叫 eventuality,給它事件處裡的方法。我們也可能回傳 that 前,在建構式裡呼叫 eventuality(that) 。這樣建構式可從一組零件組合出物件。

資料來源:《JavaScript 優良部份》 Douglas Crockford 著 歐萊禮
筆記純屬推廣及分享,如有侵權,請告知。
Please advise to remove immediately if any infringement caused.


上一篇
Day24:JavaScript 設計模式 優良部份 Chapter04 函式 筆記精要
下一篇
Day26:JavaScript 設計模式 優良部份 Chapter06 陣列 筆記精要
系列文
寇丁人妻的前端書蟲日誌30

尚未有邦友留言

立即登入留言