此模式本質上是一個函數表示式,並在定義後立刻執行。
立即函式由下面部分所組成:
- 用函式表示式定義函式
- 在函式最後加上括號,這樣會讓函式立刻執行
- 整個函式包在括號中(如果不將函式賦予給一個值才需要)
立即函式可以賦予程式碼作用域,有時候通常工作只要執行一次,沒有理由再去寫具名函式,有時候工作會需要暫時性變數,初始化後,變數會被洩露為全域變數,立即函式可以避免這個狀況。
立即函式可以傳遞參數,但不應該傳太多,這樣容易造成理解時的負擔。
立即函式可以有回傳值,而這些函式可以賦值給變數:
var result = (function (){
return 2+2;
}());
或是省略包著函式的括號,因為將立即函式的回傳值賦值給變數時不需要括號。
var result = function(){
return 2+2; //4
}();
可能會誤導,因為沒注意函式後面的括號,可能會以為 result 指向的是函式,但其實 result 是立即函式的回傳值。
但除了原始型別外,立即函式可以回傳任何型別的值,包含回傳函式,如此就可以利用立即函式私有的作用域儲存 private 資料。
這個範例中,立即函式回傳是一個函式,他會賦值給變數 getResult ,作用是簡單回傳 res ,它已經預先算好,存在立即函式的 closure。
var getResult = (function (){
var res = 2 + 2 ;
return function (){
return res;
};
}());
立即函式也可以用來定義物件屬性,假設你需要一個屬性,但在定義之前需要一些運算才能得到正確的值,而立即函式的回傳值就成為該屬性的值。
var o = {
msg: (function () {
var who = "me";
what = "call";
return what + " " + who;
}()),
getMsg: function(){
return this.msg;
}
}
o.msg //call me
o.getMsg //call me
另一種類似立即函式且避免全域污染的方式,此模式建立一個物件,並帶有 init
方法,建立物件後立即執行 init
做初始化。
({
maxwidth: 500,
maxheight: 30,
gimmeMax:function (){
return this.maxwidth + "x" + this.maxheight
},
init:function() {
console.log(this.gimmeMax();)
}
}).init();
用括號包起物件實字,關閉括號之後立即呼叫 init
,意思就是下方兩種寫法都成立。
({...}).init();
({...}.init());
優點:
將功能分開測試,並使用初始化分支做唯一一次檢查。
任何函式都具有length
屬性,用來表示預期接收的參數數量。
function func(a, b){ ... }
console.log(func.length); //2
可以隨時將你的函式新增屬性,一種自訂屬性的案例是用它來快取函式的運算結果(回傳值),快取函式的結果也被稱為記憶模式。
假設限定函式只接受原始型別的參數,如果有更複雜的參數,可以將參數序列化,化為一個 JSON 字串,並用此字串作為 catch 物件的鍵值。
序列化後,物件的識別會消失,如果有兩個不同物件但剛好有相同屬性,這兩個物件會共用同一個快取項目。
設定值模式是提供乾淨 API 的一種方式,在建立函式庫或是給其他開發者使用的程式,此模式會特別有效。
在編寫函式時需要傳遞大量參數,有一種更好的方法,就是將所有參數替換成唯一一個,讓此參數變成一個物件來表示設定值。
var conf = {
username: "batman",
first: "Bruce",
last: "Wayne"
};
addPerson(conf);
設定設定值的優點:
缺點:
在純函式的程式語言中,函式並不是被呼叫而是被應用,因為 JavaScript 的函式其實是物件而且有自己的方法。
// 定義函式
var sayHi = function (who) {
return "Hello" + (who ? "," + who : "") + "!";
};
// 呼叫函式
sayHi(); // "Hello"
sayHi('world') // 'Hello, world'
// 應用函式
sayHi.apply(null, ["hello"]); //Hello, hello
呼叫函式和應用函式的結果都相同,
apply()
需要兩個參數,第一個參數是物件,用來綁定函式內部的 this,第二個參數是參數陣列,會成為函式內可使用的類陣列 arguments 物件。如果第一個參數值是 null ,則 this 會指向全域物件,這正是當你呼叫一個非物件內方法的函式時會發生的事。
如果函式是某物件的方式,就不會像前面例子傳遞 null 參考,而物件會成為 apply 的第一個參數。
var alien = {
sayHi : function (who) {
return "Hello" + (who ? "," + who : "") + "!";
};
}
sayHi.apply(alien, ["hello"]);
除了 apply 之外,有另一個 call 方法,可省下建立陣列的工作。
程式碼的執行過程:
// 定義函式
function add(x, y){
return x + y;
}
// 知道參數並呼叫
add(5, 4);
//程式執行:步驟一
function add(5, y){
return 5 + y;
}
//程式執行:步驟二
function add(5, 4){
return 5 + 4;
}
此階段步驟一可稱為部分應用,意思是用第一個參數替換函式中的未知數,但我們並沒有得到結果(解答),而是得到另一個函式。
現在讓我們假想一個函式叫做 partialApply()
。(以下為非正規用法)
function add(x, y){
return x + y;
}
//全應用
add(5, 4);
//部分應用
var newadd = add.partialApply(null,[5]);
newadd.apply(null, [4])//9
部分運用給了另一個函式,該函式可以在之後用別的參數呼叫,等同於 add(5, 4);
,因為 add(5)
會回傳一個函式,於是可以用 (4)
呼叫,換句話說 add(5, 4);
只是 add(5)(4)
的語法糖。
這種單一輸入、單一輸出並讓函式可以理解,並處理部分應用的過程,稱為 curry 化。
curry 是一種轉換的過程——我們在轉換函式。
(拆解函式)
泛用的 curry 化函式:
function curry (fn){
var slice = Array.prototype.slice,
stored_args = slice.call(arguments, 1);
// arguments : curry 的參數。因為傳進參數的第一個參數值是 function 必須刪除,
// 但因為參數是類陣列,無法用陣列的方法,因此用 call 將 slice 指向 arguments,使arguments 為陣列,以使用陣列的方法,並刪除第一個參數。
// 如同:arguments.slice(1);
return function(){
var new_args = slice.call(arguments),
// 第二組傳入的參數,同上,將參數轉陣列
args = stored_args.concat(new_args);
//將第一組和第二組傳入的參數合併
return fn.apply(null, args);
//將合併的新參數傳入 fn
}
}
承上,新解釋範例如下:
function add (x, y){
return x + y;
}
var newadd = curry(add, 5);
newadd(4); //9
//另一種寫法
curry(add,6)(7); //13
第二個範例:
function add (a, b, c, d, e){
return a + b + c + d + e;
}
//可以轉換任何數量的參數
curry(add, 1, 2, 3)(5, 5);
//兩步驟的 curry 也可以。
var addOne = curry(add, 1);
addOne(10, 10, 10, 10); //41
當呼叫某個函式,發現傳入的參數大多相同,這時可以用柯里化,可以運用部分應用的方式,傳入一些參數給函式,並動態產生新的函式,新函式幫你保留重複的那些參數,於是不用每次都傳遞,並用他們預先填入原始函式,預期接受完整的參數列。
//不使用柯里化
add(1, 2, 3, 'abby');
add(1, 2, 3, 'leia');
add(1, 2, 3, 'jammy');
add(1, 2, 3, 'lisa');
//使用柯里化
var curryFn = curry(add, 1, 2, 3);
curryFn('abby');
curryFn('leia');
curryFn('jammy');
curryFn('lisa');
建立函式的語法:
好用的函式模式:
- API 模式:使函式介面更簡潔
- 回呼模式:將函式作為參數傳遞
- 設定值物件:幫助你讓函式的參數數量
- 回傳函式:函式的回傳值是另一個函式
- curry 化:用現有的函式加上部分參數列產生新的函式
- 初始化模式:結構化的方式來執行初始和設定,不會因暫時變數污染全域
- 立即函式:定義後立即執行
- 立即物件初始化:結構化初始工作被包進一個匿名函式中,此模式提供一個立即呼叫的方法。
- 初始階段的分支:初始階段執行唯一一次分支,而不是在應用程式的生命週期中執行許多次。
- 效能模式:
- 記憶模式:使函式屬性暫存,運算過的值不必重複運算
- 自我定義函式:函式使用新的定義覆蓋自己,在第二次呼叫過後省下工作
資料來源:《JavaScript 設計模式》 (JavaScript Patterns) Stoyan Stefanov 歐萊禮
筆記純屬推廣及分享,如有侵權,請告知。
Please advise to remove immediately if any infringement caused.