在上一篇 [筆記] 親手打造屬於你自己的 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)嗎?透過方法鍊我們可以讓一個方法接著一個方法接下去使用。
複習方法鍊:[筆記] 跟著JQuery原始碼一起學習程式設計
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 意見:
張貼留言