2016年7月22日

[筆記] 親手打造屬於你自己的 JavaScript Framework/Library(中)




在上一篇 [筆記] 親手打造屬於你自己的 JavaScript Framework/Library(上),我們開始建立了自己的 framework ,並且可以成功運用它來建立物件,但在這個物件裡面還沒有方法,因此在這篇筆記中,我們就要繼續往下做嘍。

在 framework 中建立變數


在開始建立方法(method)前,我想要先建立一些變數是我之後可以在方法中使用,但這些變數又不會和外層的 global environment 有所衝突,我們可以在哪裡建立這些變數呢?
我們可以直接將變數建立在這個 IIFEs 裡面就可以了,我們不用放在 prototype 或 function constructors 裡面,這樣會佔據額外的記憶體位置,但是,當我們需要使用時,透過 closure的概念 我們依然可以提取到這些變數。透過這種方法,我們可以讓使用 framework 的人沒有辦法去改這些變數的值,但是在使用 method 的時候,仍然可以提用到這些變數。


因此,就讓我們繼續寫下去啦。

於是我們的程式碼現在長這樣子,多了第8-23行,我分別建立了變數 supportedLangs, greeting, formalGreetings 和 logMessages 這四個變數。


  (function(global, $){
  
    var Greetr = function(firstname, lastname, language){
      return new Greetr.init(firstname, lastname, language);
    }
  
    var supportedLangs = ['en','zh-tw'];
  
    var greetings = {
      en = 'Hello',
      zh-tw = '你好'
    };
  
    var formalGreetings = {
      en = 'Greetings',
      zh-tw = '歡迎您'
    };
  
    var logMessages = {
      en = 'Logged in',
      zh-tw = '登入'
    };
  
    Greetr.prototype = {}
  
    Greetr.init = function(firstname, lastname, language){
  
      var self = this;
      self.firstname = firstname || '';
      self.lastname = lastname || '';
      self.language = language || 'zh-tw';
  
    }
  
    Greetr.init.prototype = Greetr.prototype;
  
    global.Greetr = global.G$ = Greetr;
  
  })(window, jQuery)



建立方法(method)


有兩個地方可以放置我們想要的方法,分別是如下圖的第 1 部分和第 2 部分。但是我們知道,如果放在這個 function constructor 中(第 2 部分)變成每一個所建立的物件都會直接帶有這個方法,如此會佔據相當多的記憶體空間;所以,比較好的方式是利用原型的概念,把根據這個 function constructor 所建立的物件,都可以使用到的方法,放到第 1 部分的 prototype 當中



接著,我們要來在 prototype 中開始建立一些 framework 裡面可以使用的方法。

我們先來看一下建立完方法後完整的程式碼長什麼樣子,這些程式碼都是放在Greetr.prototype{ } 內,接著我們再來分別一一解釋每個方法的意義:


  Greetr.prototype = {
    fullName: function(){
      return this.firstname + ' ' + this.lastname;
    },

    validate: function(){
      if(supportedLangs.indexOf(this.language) === -1 ){
        throw "Invalid language";
      }
    },

    greeting: function(){
      return greetings[this.language] + ' ' + this.firstname + '!';
    },

    formalGreeting: function(){
      return formalGreetings[this.language] + ' ' + this.fullName();
    },

    greet: function(formal){
      var msg;

      //  if undefined or null, it will be coerced to 'false'
      if(formal){
        msg = this.formalGreeting();
      }else{
        msg = this.greeting();
      };

      if(console){
        console.log(msg);
      };

      // 'this' refers to the calling object at execution time
      // makes the method chainable
      return this;
    },
    log: function(){
      if(console){
        console.log(logMessages[this.language] + ' ' + this.fullName());
      }

      return this;
    },

    setLang: function(lang){
      this.language = lang;

      this.validate();

      return this;
    }

  }


fullName(第3行 - 第5行):這個 method 很簡單,只是讓我們可以得到使用者的全名。

validate(第7行 - 第11行):這個 method 主要是用來檢測使用者所輸入的語言我們有沒有支援。記得我們剛剛在最上有建立了一個陣列是 supportedLangs ,裡面含有兩個元素,分別是 en 和 zh_tw,如果使用者在參數當中所輸入的語言並不屬於這兩種時,我們使用 indexOf 這個函式時,就無法在 supportedLangs 的陣列中找到,於是就會回傳 -1 的結果。此時,我們就要拋出錯誤訊息。

greeting(第13行 - 第15行)、formalGreeting(第17行 - 第19行):這兩個 method 都是讓我們跟使用者打招戶用的,其中函式裡面的 greetings 和 formalGreetings 其實是我們在前面所建立的物件,這裡我們透過括號 [ ] 的方式,來取得該物件的屬性值(因為 [ ] 內才可以放變數)。
另一個可以注意到的地方是,在 formalGreeting 這個函式的最後是使用 this.fullName( ),也就是直接呼叫並執行了前面第3行 - 第5行的這個 method。

greet(第21行 - 第38行):這個 method 稍微比較長一些。透過 greet 這個方法,我可以直接控制我要使用的是greeting 或 formalGreeting ,而不用打出這兩個方法。我們利用強制轉換的概念(coercion),如果我們給予的參數 formal 是 true 的話,那麼請幫我們使用 formalGreeting,否則使用 greeting。
為了避免有些IE版本不支援 console 這個物件,我們用 if(console) 這樣的方法,意思是如果有 console 這個物件的話,在幫我輸出。
再來很重要的一個是第37行的 return this ,記得我們前面提到的方法鍊(method chaining)嗎?透過方法鍊我們可以讓一個方法接著一個方法接下去使用。
log(第39行 - 第45行):透過這個 method 我們可以回傳登入的訊息,這同樣有使用到方法鍊的技巧。

setLang(第47行 - 第53行):透過這個 method,我們可以改變我們當初在參數中所建立的語言,同時為了驗證這個語言是不是我們所支援的,我們在這個 method 中加入了 this.validate( )來驗證,validate( )是我們在第7行 - 第11行所寫的 method 。這也同樣有使用到方法鍊的技巧。




寫到這裡,我們就差不多把我們 framework 當中要建立的方法給建立完畢啦,接著就讓我們來看看怎麼使用了!

測試所建立的 framework


接著在 app.js 中,我們可以試著來測試一下所建立的 framework 拉。

假設我輸入:


var g = G$('PJ', 'CHEN'); //  language的預設是zh-tw
g.greet().setLang('en').greet(true);


我就可以得到以下的結果。因為我們有使用了方法鍊的技巧,所以我可以一個接著一個方法的使用,當中我又用了 setLang 這個方法,把預設的語言改成英文:


接著,假設我輸入:

var g = G$('PJ', 'CHEN'); //  language的預設是zh-tw
g.log().setLang('zh_cn').greet(true);

首先,會回傳登入的訊息,接著因為我把語言設成 "zh_cn" 但實際上,我們並不支援這樣的語言,所以回拋出錯誤的訊息給我們。大家可以自己測試自己所寫的 framework 來測試玩玩看。


程式範例


(function(global, $){

  var Greetr = function(firstname, lastname, language){
    return new Greetr.init(firstname, lastname, language);
  }

  var supportedLangs = ['en','zh_tw'];

  var greetings = {
    en: 'Hello',
    zh_tw: '你好'
  };

  var formalGreetings = {
    en: 'Greetings',
    zh_tw: '歡迎您'
  };

  var logMessages = {
    en: 'Logged in',
    zh_tw: '登入'
  };

  Greetr.prototype = {
    fullName: function(){
      return this.firstname + ' ' + this.lastname;
    },

    validate: function(){
      if(supportedLangs.indexOf(this.language) === -1 ){
        throw "Invalid language";
      }
    },

    greeting: function(){
      return greetings[this.language] + ' ' + this.firstname + '!';
    },

    formalGreeting: function(){
      return formalGreetings[this.language] + ' ' + this.fullName();
    },

    greet: function(formal){
      var msg;

      //  if undefined or null, it will be coerced to 'false'
      if(formal){
        msg = this.formalGreeting();
      }else{
        msg = this.greeting();
      };

      if(console){
        console.log(msg);
      };

      // 'this' refers to the calling object at execution time
      // makes the method chainable
      return this;
    },
    log: function(){
      if(console){
        console.log(logMessages[this.language] + ' ' + this.fullName());
      }

      return this;
    },

    setLang: function(lang){
      this.language = lang;

      this.validate();

      return this;
    }

  }

  Greetr.init = function(firstname, lastname, language){

    var self = this;
    self.firstname = firstname || '';
    self.lastname = lastname || '';
    self.language = language || 'zh_tw';

  }

  Greetr.init.prototype = Greetr.prototype;

  global.Greetr = global.G$ = Greetr;

})(window, jQuery)




→回到此系列文章目錄



0 意見:

張貼留言