JavaScript 設計原則-2

by vivid 1. 六月 2016 17:56

 

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N160617201

出刊日期:2016/6/1
開發工具:Visual Studio 2015 Update 1

在《JavaScript 設計原則-1》這篇文章之中,我們介紹了一些JavaScript重要的設計原則來開發應用程式,遵循一些設計原則來撰寫程式碼,這樣的好處是後續要維護這個系統的人,會更容易進入狀況,快速上,手,也能更容易的進行除錯的動作,找出應用程式的問題所在。本篇文章將延續前文的概念,繼續來介紹一些常用的設計原則或開發模式。

使用模組(Module)設計模式

我們繼續來探討前文提及的模組(Module)設計模式範例。現在先假設一個情況,若模組(Module)的程式碼是放在一個外部檔案,檔名為Module1.js,檔案中包含以下myModule程式碼,模組中包含一個private 變數x、一個 printValue(private function)函式。myModule 回傳一個物件,定義x屬性開放外部程式存取內部變數,與public函式供外部程式呼叫:

//Module1.js檔案
var myModule = (function () {
  //private 變數
  var x = 1;

  //private function
  function printValue() {
    console.log(x);
  }

  //回傳一個物件開放外部存取
  return {
    x: x, //public變數
    printValue: printValue, //public function
    inc: function () { x++; } //public function
  };
}());

 

再假設我們在一個HTML的網頁引用這個檔案的內容,並且加入以下<script>區塊的程式碼,在網頁之中,便可以叫用到Module中的程式碼,我們利用屬性的語法「.」符號來存取x變數的值,叫用inc()與printValue()函式:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
</head>
<body>
  <script src="Module1.js"></script>
  <script>
    console.log(myModule.x); //1
    myModule.inc();
    myModule.printValue(); //2
  </script>
</body>
</html>

 

不過,你可能會遇到一種情況,當一個Module的程式太多時,你可能會希望將Module的程式碼分散到別的JavaScript檔案之中。若要解決這個問題,我們可以這樣做,在其它的JavaScript檔案中,加入原來的myModule的定義,例如下列的範例程式碼,我們將此程式儲存在Module2.js檔案之中,使用Immediate Invoked Functions語法定義myModule,並且將myModule當做是函式的的參數傳遞到函式之中,在函式中我們可以再為myModule新增新的函式,或是屬性,最後再將這個模組回傳:

//Module2.js檔案
var myModule = (function (mod) {

  mod.alertValue = function (s) {
    alert(s);
  }
  return mod;
}(myModule));

 

 

那麼我們就可以在網頁之中,匯入這兩個同時包含myModule定義的JavaScript檔案,並且呼叫定義在兩個模組檔案之中的方法,參考以下範例程式碼:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
</head>
<body>
  <script src="Module1.js"></script>
  <script src="Module2.js"></script>
  <script>
    console.log(myModule.x); //1
    myModule.inc();
    myModule.printValue(); //2
    myModule.alertValue("hello");
  </script>
</body>
</html>

使用參數

上一個例子雖然解決跨檔案存放模組程式的問題,但是它有一個缺點,便是引用JavaScript的順序必需先是Module1.js,然後再引用Module2.js檔案,若檔案的順序相反,則alertValue函式就沒有辦法正確執行,參考以下範例程式碼:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
</head>
<body>
  <script src="Module2.js"></script>
  <script src="Module1.js"></script>
  <script>
    console.log(myModule.x); //1
    myModule.inc();
    myModule.printValue(); //2
    myModule.alertValue("hello");
  </script>
</body>
</html>

 

這個網頁執行時會印出錯誤訊息,參考如下圖所示:

clip_image002

雖然你可以修改程式碼中引用JavaScript檔案的順序來避免掉這個問題,但未來若使用非同步的技巧來下載這兩個檔案,我們並無法預期未來哪個檔案會先下載下來,那麼這個解法並不能真的解決掉這個問題。參考以下解法,我們可以在定義模組時,傳入myModule,或建立一個新的物件來解決這個問題,修訂Module1.js程式如下:

//Module1.js
var myModule = (function ( mod ) {
  //private 變數
  var x = 1;

  //private function
  function printValue() {
    console.log( x );
  }
 
  mod.x = x; //public變數
  mod.printValue = printValue; //public function
  mod.inc = function () { x++; }; //public function
  //回傳一個物件開放外部存取
  return mod;
}(myModule || {}));

修訂Module2.js程式如下:
var myModule = (function (mod) {

  mod.alertValue = function (s) {
    alert(s);
  }
  return mod;
}(myModule || {}));

 

 

如此就可以解決JavaScript下載檔案順序性不一定的這個問題。

使用方法串接(Method Chaining)模式

方法串接(Method Chaining)常見於JavaScript程式碼,目地是用來簡化針對一個物件呼叫多次方法時的程式碼。例如以下JavaScript程式碼,宣告一個s變數,重複叫用多次String物件的concat方法,將a、b、c、d串接在一起,最後再叫用toUpperCase()將字串全部轉大寫:

var s = "a"
.concat("b")
.concat("c")
.concat("d")
.toUpperCase();
console.log( s ); //ABCD

方法串接(Method Chaining)也經常出現於jQuery程式,例如以下網頁範例,利用jQuery的css方法設定div的背景顏色與前景顏色,然後透過animate()執行動畫,在2秒內,將寬度、高度設為300px:

<!DOCTYPE html>
<html>
<head>
  <title></title>
  <meta charset="utf-8" />
  <style>
    #div1 {
      border: 1px solid blue;
      width: 10px;
      height: 10px;
    }
  </style>
</head>
<body>
  <div id="div1">demo</div>
  <script src="Scripts/jquery-2.2.3.js"></script>
  <script>
    $(function () {
      $( "#div1" ).css( 'backgroundColor', 'yellow' )
        .css( 'color', 'red' )
        .animate( { height: '300px', width: '300px' } , 2000 );
    });
  </script>
</body>
</html>

 

我們自訂JavaScript物件時,可以套用這個模式,只要讓物件的函式回傳this,這樣就可以利用方法串接的方式來叫用物件的程式碼。以下StringDemo程式碼範例模擬上文的字串串接程式:

function StringDemo( str ) {
  this.concat = function ( s ) {
    str = str + s;
    return this;
  }

  this.toUpperCase = function () {
    str = str.toUpperCase();
    return this;
  }


  this.getResult = function ( callback ) {
    callback( str );
    return this;
  }
}


 

這樣我們就可以建立一個StringDemo物件,重複叫用concat方法串字串,接著轉換字串為大寫,最後提供一個getResult方法,利用callback函式,在console印出最後字串的內容:

new StringDemo( "a" )
  .concat( "b" )
  .concat( "c" )
  .concat( "d" )
  .toUpperCase()
  .getResult(function ( result ) {
    console.log( result );
  });

使用Closure

由於JavaScript的this是一個非常特殊的物件,它可能在執行階段被修改,為了保險起見,建議透過Closure來保護它,在StringDemo函式中,宣告一個變數,來記住this,通常變數的名稱習慣命為self,修改程式碼如下:

function StringDemo( str ) {
  var self = this;
  this.concat = function ( s ) {
    str = str + s;
    return self;
  }

  this.toUpperCase = function () {
    str = str.toUpperCase();
    return self;
  }


  this.getResult = function ( callback ) {
    callback(str);
    return self;
  }
}

new StringDemo( "a" )
  .concat( "b" )
  .concat( "c" )
  .concat( "d" )
  .toUpperCase()
  .getResult(function ( result ) {
    console.log( result ); //ABCD
  });

 

設計屬性異動通知功能

若物件的值變動時,想要得到通知,可以為物件設計Observable屬性,通常都應用在事件的設計動作。由於JavaScript的方法可以當做屬性來使用,因此你可以利用這個特色,改用方法來取代屬性的寫法,並在方法中撰寫程式碼,進行異動通知動作。

參考以下範例程式碼,定義一個Stock建構函式,內部定義了一個notifyList陣列,目地是用來記錄所有要得到通知的回呼程式。Stock建構函式定義兩個方法,用來記錄股票的名稱與價格。若呼叫端叫用onPriceChanged方法時,便將回呼函式記錄到notifyList陣列之中,後續當股票的價格變動時,我們利用一個迴圈,執行notifyList陣列之中的回呼函式,來通知異動:

function Stock( name, price ) {
  var notifyList = [];
  this.name = function ( val ) {
    return name;
  };
  this.stockPrice = function ( val ) {
    if (val !== undefined && val !== price) {
      price = val;
      for (var i = 0; i < notifyList.length; i++) {
        notifyList[i](this);
      }
    }
    return price;
  };
  this.onPriceChanged = function ( callback ) {
    notifyList.push(callback);
  };
}

var aStock = new Stock( "StockA", 50.5);
console.log( 'The stock name is ' + aStock.name());
console.log( 'The stock price is NT$' + aStock.stockPrice());

aStock.onPriceChanged( function ( b ) {
  console.log( 'The stock price has changed to : NT$ ' + b.stockPrice() );
});

aStock.stockPrice( 52.1 );
aStock.stockPrice( 60 );


 

這個範例的執行結果如下所示,每回叫用stockPrice方法變更股票價格時,就會印出價格變動的訊息:

The stock name is StockA

The stock price is NT$50.5

The stock price has changed to : NT$ 52.1

The stock price has changed to : NT$ 60

Tags:

.NET Magazine國際中文電子雜誌 | JavaScript

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List