用JavaScript寫類別的淺薄心得 - Method篇
還記得剛開始上C++, 一值不斷強調的重要觀念就是封裝
一直到後來已經脫離不離物件概念去跑程式
不管是把常用的工具寫成一個類別
或著是定義好屬性、方法的一個類別
在程式裡面總是充滿了這些東西在互相運作
而在AJAX上面, 其實能的話我們最好也是用類別、物件的感覺去處理程式的片段
不管在維護上或是閱讀上都會比較清楚
這是一個很深奧的學問, 我自認沒有資格對這個議題去做什麼分享
充其量就是一些個人在使用上的小心得而已
或許有些錯誤的地方, 就請再指正了
其實說穿了 JavaScript本身沒有提供一個完整的架構去實作類別
於是很多人就利用了JavaScript本身充滿彈性的物件類別
去模擬做出一個自己的類別,
方法千百種, 當然各有利弊
怎麼作才是一個完美的作法, 其實我也在找
對我而言會希望一個架構可以很清楚的定義出來屬性在哪邊找, 方法有哪些
建構子寫在哪邊, 如何區隔出static方法與非static的方法
一個清楚的架構規範有助於自己編寫程式, 以及好維護
也不會給之後接手的人帶來很大的困擾
以下的程式碼只能說是我的一個習慣
就提供做為參考了
=================================================================================================
一、靜態的函式庫
有的時候我們會寫一個例如說
public static class Utility {...} 一些個人寫程式的小工具
public class Utility
{
public static void JavaAlert(Control my, string szMessage)
{
ScriptManager.RegisterClientScriptBlock(my, my.GetType(),
string.Format("SendMsg{0}", my.ClientID),
string.Format("alert('{0}');", szMessage.Replace("'", "\\'")), true);
}
//.....
}
這種類別裡面大部分都是靜態Method, 不需要new出來就可以直接用 Utility.JavaAlert(this, "test") 直接呼叫的個人函式庫
要是對應到JavaScript該怎麼作呢?
例如說現在假如我們有一個自己寫的小工具, 可以讀出目前的QuertString
function queryStr() {
var AllVars = window.location.search.substring(1);
var Vars = AllVars.split("&");
for (i = 0; i < Vars.length; i++) {
var Var = Vars[i].split("=");
if (Var[0] == name) return Var[1];
}
return "";
}
然後在不同頁面用到就重複寫一次,
或是就算沒重複, 類似功能的小function也散落在專案各處
很難管理跟維護
要是我們想要跟C#一樣用個utiltiy的類別把他裝起來,
首先我們假設有一個叫做global.js, 這個檔案會在MasterPage匯入, 也就是說每一頁都會讀取到
然後我們可以在裡面加這段程式
var utility = { //共用的AjaxOnError函式
parseId: function (szInput) { //取得數字,若不是數字就回傳-999
if (szInput == null || szInput == undefined || !szInput) return -999;
var int = parseInt(szInput, 10);
return isNaN(int) ? -999 : int;
},
alertArgs: function () { //測試用的函數,把傳入的值用逗號分割後alert
var msg = '';
for (var i = 0; i < arguments.length; i++) {
msg += arguments[i] + ', ';
}
alert(msg.substr(0, msg.length - 2));
},
queryStr: function (name) { //取得QuertString
var AllVars = window.location.search.substring(1);
var Vars = AllVars.split("&");
for (i = 0; i < Vars.length; i++) {
var Var = Vars[i].split("=");
if (Var[0] == name) return Var[1];
}
return "";
},
pathName: function () {
return location.pathname.substring(location.pathname.lastIndexOf('/') + 1) + '/';
}
};
這樣可以很明顯的看出來, 我們把所有類似功能的function通通都放在utility這個物件裡面
之後我們就可以在不同頁面上使用, 例如說
alert( utility.queryStr('test') );
感覺起來其實就跟C#類別的靜態方法一樣吧
這樣做的好處首先就是程式碼可以重複利用, 不用每頁都寫同樣的程式碼
而且好維護, 只要自己養起好習慣, 類似功能的程式都放到utility底下
要修改要增加其實都很方便
這邊其實可以順便講一下, 為什麼可以這樣宣告跟使用
JavaScript是一個非常活的語言, 不像.NET一樣嚴謹
對於物件而言, 什麼東西都可以是他的屬性, 包括function
所以我們宣告一個叫做utility的物件
在把許多的function指定為他的屬性
因此自然可以用{objName}.{attr}的方法去存取那些方法
所以在這邊可以看得出來, JavaScript本身沒有什麼靜態類別這種東西
只是利用他本身的特性做出類似效果的東西而已
因此絕對沒有什麼制式或規定的寫法 (除了jQuery UI外, 之後有機會再聊)
充其量就是自己寫的順手, 不要給後人造成麻煩
其他就是工程師的基本功了, 如何在這樣的架構上平衡效能與功能
這些遇到再慢慢加強就好
=================================================================================================
二、擴充現有的類別
猶豫了一下, 決定還是把這個主題加進來
ASP.NET在3.5之後(?), 有一個好用的功能
我們可以在既有類別寫好之後, 幫他擴充新的功能進去
例如說
public static class Extension
{
public static string Append(this string str, string szAppend)
{
return str + szAppend;
}
}
我們可以用這個方法擴充既有的string類別
之後就可以在程式碼中使用
string test = "abc";
test.Append("def");
可以看到string本身並沒有Append這個方法, 但是我們可以幫他擴充這個方法進去
而不用改寫原本的Class
為什麼這邊要提到這件事呢
JavaScript本身有提供一些現有的類別,例如Date, Number, Array之類的東西
而我們免不了要處理這些函數
例如說我們想要直接這個時間的月份, 而不想要每次取得月份還要手動+1
可能會寫一個function
fucntion getRealMonth(var date) {
return date.getMonth() + 1;
}
可是要是我們希望擴充原本的Date函式, 讓他能直接多一個getRealMonth()的功能
其實也是有辦法
就用JavaScript的prototype, 在global.js加入這段code
Date.prototype.getRealMonth = function () {
return this.getMonth() + 1;
};
這段話的用意就是說, 把Date的prototype擴充一個方法叫做getRealMonth, 這樣之後我們可以這樣呼叫
var date = new Date();
alert(date.getRealMonth());
是不是變得直覺了許多
又回到問題, 為什麼能這樣寫
其實這個問題就建議去google一下什麼叫做prototype
這個東西其實就可以另外再開一個章節來詳述
為了避免半吊子的東西講不清楚
就不在這邊多提了
=================================================================================================
三、擴充現有的類別 (靜態方法)
要是說我們現在希望有一個功能叫做isDate, 判斷這個字串是不是時間格式
對專案來說, 這個類別跟Date有關, 我們應該要把他放在Date類別裡面
這樣假如要維護要修改比較好找位置
可是這種東西其實沒必要寫在prototype裡面
對我們而言他比較偏向是Date的靜態方法
那這要怎麼作?
其實就跟第一個主題一樣, 直接寫
Date.isDate = function (date) {
return !isNaN(Date.parse(date));
};
這樣就可以直接下
alert( Date.isDate('2010/11/31') );
坦白說這個主題其實跟主題一完全重複, 為什麼要再另外一個主題來講這個。
其實只是在重複一個觀念
JavaScript的object類別非常的活
他可以把任何的東西當成他的屬性, 也可以在程式碼的任何地方直接存取與操作屬性 ( 此點不完全盡然, 我們也是可以用特殊方法寫出private屬性, 之後的篇幅會再提到 )
而不管是Array, Number, Date 充其量也只是內建的比較特殊的object
但還是個object
因此我們也可以很自由的操作他的屬性
=================================================================================================
三、覆寫原有的方法
假如說現在希望把Date.getYaer()的功能給複寫, 希望getYear()的時候回傳的是民國的年份, 其實方法也很簡單
Date.prototype.getYear = function () {
return '民國' + ( this.getFullYear() - 1911 ) + '年';
};
var date = new Date();
alert(date.getYear());
這樣呼叫會發現本來應該要傳"2011"的值, 卻變成了"民國100年”
不過非常不鼓勵這樣做, 問題會非常多
只是既然不鼓勵, 為什麼這篇會提到
其實這只是在重複驗證一件事
我們可以在程式的任何地方
去操作物件的任何屬性
就算是JavaScript本身所定義好的getYear方法
也可以被我們用新的function給取代
其實對我而言, 這變成超可怕的事情
因為這表示說, 我寫的類別 裡面所有的屬性都變得不可靠
當他是public出去讓別人使用的時候
別人可以任意操作裡面的屬性
而在呼叫我寫好的方法的時候, 我本來要存取的屬性可能就會因此而不見
然後程式就包了 = =
我個人是有切身之痛 orz
在這邊最後可以提到,
我們可以用 {objName}.{newAttr} = newValue; 去針對這個物件加一個新的屬性進去
也可以用 {objName}.property.{newAttr} = newValue; 去針對這個物件類別的所有new出來的物件加一個新的屬性進去
那我們假如想要把屬性給移除要怎麼作
{objName}.{newAttr} = undefined; 這樣就好
本來這邊想要寫怎麼新增一個類別
不知不覺講了這麼多還沒講到主題..只好等下一篇再說了 orz
--
本文可能有理解錯誤 或不盡不實的地方
請路過的前輩不要客氣 用力打醒
這會是我們成長的主要養分