iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 4
0
自我挑戰組

寇丁人妻的前端書蟲日誌系列 第 4

Day04:JavaScript 設計模式 第四章函式 筆記精要(下)

立即函式

此模式本質上是一個函數表示式,並在定義後立刻執行。
立即函式由下面部分所組成:

  • 用函式表示式定義函式
  • 在函式最後加上括號,這樣會讓函式立刻執行
  • 整個函式包在括號中(如果不將函式賦予給一個值才需要)

立即函式可以賦予程式碼作用域,有時候通常工作只要執行一次,沒有理由再去寫具名函式,有時候工作會需要暫時性變數,初始化後,變數會被洩露為全域變數,立即函式可以避免這個狀況。

立即函式的參數

立即函式可以傳遞參數,但不應該傳太多,這樣容易造成理解時的負擔。

立即函式的回傳值

立即函式可以有回傳值,而這些函式可以賦值給變數:

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);

設定設定值的優點:

  • 不需要記住參數和順序
  • 可以更安全略過選用參數
  • 更容易閱讀和維護
  • 更容易新增和移除參數

缺點:

  • 需要知道參數名稱
  • 設定值物件的屬性名稱無法被最小化

Curry

函式的應用

在純函式的程式語言中,函式並不是被呼叫而是被應用,因為 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 是一種轉換的過程——我們在轉換函式。
(拆解函式)

泛用的 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


使用 Curry 的時機

當呼叫某個函式,發現傳入的參數大多相同,這時可以用柯里化,可以運用部分應用的方式,傳入一些參數給函式,並動態產生新的函式,新函式幫你保留重複的那些參數,於是不用每次都傳遞,並用他們預先填入原始函式,預期接受完整的參數列。

//不使用柯里化
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');

總結

  • JavaScript 是第一級物件,他們可以和原始型別值)一樣作為參數傳遞,也可以擴充屬性和方法。
  • 函式提供區域作用域,而大括號產生的區塊則沒有,區域變數的宣告會被提升到區域作用的頂端。

建立函式的語法:

  • 具名函式表示式
  • 函式表示式:和上一個相同,只是缺少名字,也稱為匿名函式
  • 韓式宣告式:類似其他語言

好用的函式模式:

  • API 模式:使函式介面更簡潔
    • 回呼模式:將函式作為參數傳遞
    • 設定值物件:幫助你讓函式的參數數量
    • 回傳函式:函式的回傳值是另一個函式
    • curry 化:用現有的函式加上部分參數列產生新的函式
  • 初始化模式:結構化的方式來執行初始和設定,不會因暫時變數污染全域
    • 立即函式:定義後立即執行
    • 立即物件初始化:結構化初始工作被包進一個匿名函式中,此模式提供一個立即呼叫的方法。
    • 初始階段的分支:初始階段執行唯一一次分支,而不是在應用程式的生命週期中執行許多次。
  • 效能模式:
    • 記憶模式:使函式屬性暫存,運算過的值不必重複運算
    • 自我定義函式:函式使用新的定義覆蓋自己,在第二次呼叫過後省下工作

資料來源:《JavaScript 設計模式》 (JavaScript Patterns) Stoyan Stefanov 歐萊禮
筆記純屬推廣及分享,如有侵權,請告知。
Please advise to remove immediately if any infringement caused.


上一篇
Day03:JavaScript 設計模式 第四章函式 筆記精要(上)
下一篇
Day05:JavaScript 設計模式 心得
系列文
寇丁人妻的前端書蟲日誌30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言