Zi 字媒體
2017-07-25T20:27:27+00:00
此篇文章瀏覽量:
1,190
此文章摘錄自’JavaScript優良部分’一書,純粹個人備忘之用。如欲看全文,請參閱該書。
函式物件
JavaScript的函式都是物件。函式物件聯繫到Function.prototype(本身再聯繫到Object.prototype)。既然函式是物件,就能像其他任何值一樣使用。函式能儲存在變數、物件或陣列裡。函式能被當成引數傳給另一個函式、函式也能被另一個函式回傳。最後,函式既然是物件,它也可以有方法。函式能被呼叫。
函式實字
函式物件以函式實字建立:
//建立變數add,其中儲存一個相加兩數的函式
var add = function (a, b){
return a + b;
}
函式實字有四個部份
保留字function
函式名稱(選用),以上述範例來說,並沒有函式名稱。所以稱為匿名函式(anonymous function)
函式參數的集合
一組以大括號圍起的敘述,此敘述是函式的本體,在函式被呼叫時執行
函式實字建立的函式物件,包含對外圍環境的聯繫,稱為closure(閉包),這是種具有無窮表達能力的資源。
方法呼叫模式
// 建立 myObject 物件,其中有個值value
// 以及一個執行遞增計算的方法
// 遞增方法可接收一個選用參數
// 如果引數不是數值,則使用預設值1。
var myOjbect = {
value: 0;
increment: function(inc){
this.value += typeof inc === 'number' ? inc : 1;
}
};
myObject.increment();
document.writeln(myObject.value); //輸出1
myObject.increment(2);
document.writeln(myObject.value); //輸出3
函式呼叫模式
當函式不是物件的特性時,則像函式一般被呼叫:
var sum = add(3, 4);
使用此種呼叫模式時,this將結合至全域變數,這點是語言設計上的錯誤。如果設計正確,呼叫內層函式時,this應該仍會結合至外層函式的this。這項錯誤的後果之一,使得方法無法利用內層函式協助工作,因為內層函式並未享有方法對物件的存取,它的this結合到錯誤的地方了。有個輕鬆的解決之道:如果方法定義一個變數,並指派它的值為this,內層函式將透過這個變數存取this。一般習慣把這個變數命名為that:
// 引數myObject具有double方法
myObject.double = function(){
var that = this;
var helper = function(){
that.value = add(that.value, that.value);
};
helper(); // 把helper視為函式而呼叫
};
// 把double視為函式而呼叫
myObject.double();
document.writeln(myObject.getValue()); // 輸出6
建構式呼叫模式(不建議使用)
// 建立稱為Quo的建構式
// 它讓物件具有status的特性
var Quo = function(string){
this.status = string;
};
//為所有Quo的實例,賦與一個稱為get_status的public方法
Quo.prototype.get_status = function(){
return this.status;
};
//製作一個Quo實例
var myQuo = new Quo("confused");
document.writeln(myQuo.get_status()); // 輸出confused
與字首詞new合用的函式,稱為建構式(constructor)。
apply呼叫模式
apply方法讓我們建構一個引數陣列,用於呼叫函式,也能讓我們使用this的值。apply方法接受兩個參數,第一個是應該與this聯繫的值,第二個則是參數的陣列。
// 製作一個包含兩個數字的陣列,並予以相加
var array = [3, 4];
var sum = add.apply(null, array); // 總和為7
// 製作一個具有status成員的物件
var statusObject = {
status: 'A-OK'
};
// statusObject並非從Quo.prototype繼承而來
// 但我們可以在statusObject上呼叫get_status,
// 儘管 statusObject 並不擁有get_status方法
var status = Quo.prototype.get_status.apply(statusObject); // status是A-OK
arguments陣列
var sum = function(){
var i, sum = 0;
for(i = 0; i < arguments.length; i += 1){
sum += arguments[i];
}
return sum;
};
document.writeln(sum(4, 8, 15, 16, 23, 42)); // 108
上例並非特別好用的模式,因為設計上的錯誤,arguments並非真正的陣列,它是個像陣列的物件。arguments具有length特性,但缺乏所有陣列方法。
回傳
函式一定回傳某個值。如果未指定return值,則回傳undefined。
如果函式呼叫時附有字首詞new,而且return值不是物件,則改為回傳this(新物件)。
例外狀況
try敘述只有一個捕捉所有例外事件的catch區塊。如果你的處理方式需依照例外類型而定,則例外處理器必須檢視name,以判斷例外類型。
// throw敘述負責中斷函式的執行。它應該要拿到一個exception物件,物件中包含辨識例外類別的name特性,
// 以及描述例外性質的message特性。你也可以再多加其它特性:
var add = function(a, b){
if(typeof a !== 'number' || typeof b !== 'number'){
throw {
name: 'TypeError',
message: 'add needs numbers'
}
}
return a + b;
};
// 製作try_it函式,不正確地呼叫新的加法函式,
// exception物件將被傳遞給try敘述的catch字句:
var try_it = function(){
try{
add('seven');
} catch(e){
document.writeln(e.name + ': ' + e.message);
}
};
try_it();
擴充型別
以擴充Function.prototype為例,增加一個方法到所有函式下:
// 添method方法後,我們可不再需要鍵入prototype特性的名稱
Function.prototype.method = function(name, func){
if(!this.prototype[name]){
this.prototype[name] = func;
}
return this;
};
JavaScript沒有獨立的整數型別(integer),所以有時需要單獨抽離數值中的整數部分。我們可以為Number.prototype添加一個integer方法:該方法可能使用Math.ceiling或Math.floor產生整數,根據數值的正負號而決定:
Number.method('integer' function(){
return Math[this < 0 ? 'ceiling': 'floor'](this);
});
// 例:
document.writeln((-10 / 3).integer()); // -3
JavaScript缺少移除字串尾端空格的方法,以下可解決:
String.method('trim', function(){
return this.replace(/^\s+|\s+$/g, '');
});
// 例:
document.writeln('"' + " neat ".trim() + '"');
範圍
var foo = function(){
var a = 3, b = 5;
var bar = function(){
var b = 7, c = 11;
//此處:a是3,b是7,c是11
a += b + c;
//此處:a是21,b是7,c是11
};
//此處:a是3,b是5,c未定義
bar();
//此處:a是21,b是5
};
closure(閉包)
示範如何保護值不被未授權的來源隨意改變。
此時不再以物件實字做myObject的初始化,我們將透過呼叫一個回傳物件實字的函式而初始化myObject。這個函式定義了value變數,變數仍可被increment與getValue方法取用,但函式範圍把它隱藏起來,不讓程式的其餘部分見到:
// 我們並非把函式本身指派給myObject,而是把函式呼叫的結果指派過去。請注意最後一行的()。
// 函式回傳一個包含兩個方法的物件,這兩個方法則繼續享有取用value變數的特權。
var myObject = function(){
var value = 0;
return {
increment: function(inc){
value += typeof inc === 'number' ? inc : 1;
},
getValue: function(){
return value;
}
}
}();
稍早有提過Quo建構式,它會產生具有status特性與get_status方法的物件。只是這沒意思。可以直接取用的特性時,何必繞遠路呼叫getter方法?如果status是個private特性將更為有用,讓我們定義一個不同的quo函式:
// 建立一個稱為quo的函式
// 製作具有get_status方法
// 與private status特性的物件
var quo = function(status){
return {
get_status: function(){
return status;
}
};
};
// 製作一個quo實例
var myQuo = quo("amazed");
document.writeln(myQuo.get_status());
上述的quo函式,設計為不需使用字首詞new,所以名稱也不需首字母大寫。當我們呼叫quo,它回傳包含get_status方法的新物件。對該物件的參考則儲存在myQuo。get_status方法仍有對quo的status特性的優先存取權,儘管quo已經被回傳了。get_status對參數複本沒有存取權,而是對參數本身有存取權。都是因為函式能取用建造它本身的背景情境,才有這種可能。這種狀況稱為closure(閉包)。
模組
我們可以使用函式與closure製造模組。模組是個函式或物件,用於呈現一個介面,但隱藏起它的狀態與實作。
// 為String擴充一個deentityify方法,它的功能是在字串裡尋找html實體(html entity),並以意義相等的字元取代。
// 在物件裡保存實體名稱及其相等事物頗為合理,但物件又該放在哪裡?最理想的方式,是把物件放入closure,或許再提供
// 一個新增實體的方法
String.method('deentityify', function(){
// 實體表。實體的名稱與字元的對照。
var entity = {
quot: '"',
lt: ''
};
// 回傳deentityify方法
return function(){
// 以下為deentityify方法。它呼叫負責字串取代的方法。
// 尋找以&起始、以;結尾的子字串。如果其間的字元
// 儲存在實體表中,則以相等字元取代實體。
// 這個方法用到正規運算式
return this.replace(/&([^&;]+);/g,
function(a, b){
var r = entity[b];
return typeof r === 'string' ? r : a;
});
};
})();
// 以上請注意最後一行。我們立即呼叫使用()運算子製造的函式。此時的呼叫將建立並回傳變旁deentityify的函式。
document.writeln(''.deentityify());
模組模式,一個「定義private變數與函式」的函式;它建立可(透過closure)取用private變數與函式的特許函式;而後把特許函式回傳或儲存於可存取的地方。
使用模組模式,可削減全域變數的使用。如此一來推廣了資訊隱藏及其它良好的設計習慣;在壓縮應用程式和其他singletons時非常有效。
模組也能用於製作具有安全性的件。假設想製作一個產生連續流水號的物件:
var serial_maker = function(){
// 製作一個產生具唯一性字串的物件。
// 唯一性字串由兩個分組成:字首、與連續流水號。
// 物件包括設定字首與流水號的方法
// 還有一個產生唯一字串的gensym方法
var prefix = '';
var seq = 0;
return {
set_prefix: function(p){
prefix = String()p;
},
set_seq: function(s){
seq = s;
},
gensym: function(){
var result = prefix + seq;
seq += 1;
return result;
}
};
}();
var seqer = serial_maker();
seqer.set_prefix = 'Q';
seqer.set_seq = 1000;
var unique = seqer.gensym(); //字串unique為"Q1000"
上例方法並未使用this與that。結果,則是不會外洩的seqer;除了同樣獲得方法的允許,否則無法取得或改變prefix或seq。seqer物件是可變的,所以方法雖可代取,但仍然不會開放對其秘密資料的存取。seqer不過是函式的集合,這些函式則保證具有使用或調整秘密資料的特殊能力。
如果我們把seqer.gensym傳送給第三方函式,函式將能產生唯一字串,但無法改變prefix或seq。
若覺得文章有幫助,請多分享,讓其他同好也能吸收網站技能知識。
Tweet
寫了
5860316篇文章,獲得
23313次喜歡