JavaScript Cookbook 2nd 之 Function
昨晚翻了一下,雖然都是一些舊知識,不過深入下去對照著其他資料一起看,還是能發現一些有意思的地方。
函數式編程
反正之前我是沒搞懂函數式和命令式的區別,也很疑惑函數式編程中,如果出現分支怎么辦,昨晚總算弄明白了。
// 我們有4個基礎函數,會根據不同的業務邏輯進行組裝使用
// 自動創建
function autoCreate () {}
// 自動同步
function autoSync () {}
// 流程 A
function processA () {}
// 流程 B
function processB () {}
// 流程 A 與流程 B 在業務上是互斥的
傳統的命令式編程,我們會這樣寫業務邏輯
function service (errorHandler) {
var result;
if (!id) {
result = autoCreate();
if (result.error) {
errorHandler(result);
}
}
if (type === 'a') {
processA();
if (result.error) {
errorHandler(result);
}
}
if (type === 'b') {
result = processB();
if (result.error) {
errorHandler(result);
}
}
if (!isSync) {
result = autoSync();
if (result.error) {
errorHandler(result);
}
}
}
而函數式編程,我們則可以這樣寫業務邏輯。
// service 本身不是一個異步業務,所以直接使用 Promise.resolve()
var service = Promise.resolve();
// 需要對autoCreate()等四個基礎函數做 Promise 改造
service.then(autoCreate)
.then(processA)
.then(processB)
.then(autoSync)
.catch(errorHandler);
這里可能會有一個疑惑,互斥的 processA 和 processB 怎么進入了同一個處理流程,這樣和需求就不符合了?
在這種情況下,我們還需要在 processA 和 processB 的內部,把退出條件補上。
function processA () {
return new Promise(function(resolve, reject){
if (type !== 'A') {
resolve();
}
// 這里繼續 processA 的邏輯代碼
});
}
調用棧
JS 在執行的時候,有一個函數調用棧,棧里面放著一個個的函數調用幀,這些幀保存著所屬函數所需的所有變量信息。
函數調用會在內存形成一個“調用記錄”,又稱“調用幀”(call frame),保存調用位置和內部變量等信息。如果在函數A的內部調用函數B,那么在A的調用幀上方,還會形成一個B的調用幀。等到B運行結束,將結果返回到A,B的調用幀才會消失。如果函數B內部還調用函數C,那就還有一個C的調用幀,以此類推。所有的調用幀,就形成一個“調用棧”(call stack)。
瀏覽器攔截 window.open
我們發現有時候執行 window.open() ,能正常打開新窗口或者新的標簽頁,而有時卻又不行,會被瀏覽器攔截。
其原因是瀏覽器會根據當前調用棧,找到最初的caller,如果不是用戶觸發的,則攔截。
尾調用優化
由于函數調用的時候會生成新的調用幀,當遞歸調用的時候,調用棧中的調用幀增長會非常厲害,最終導致內存耗盡而觸發 RangeError。
如果把函數調用放在函數塊的最后一條語句,且不在使用外層函數的變量了,則外層函數所占用的調用幀已無存在意義。在進入內層函數的時候,可以直接用內層函數的調用幀替換掉外層函數的調用幀,從而大大減少內存占用。
其他
Partial Application 的模式,用來做 Library 和 SDK 都挺好的。一個可定制性高的底層接口,再通過類似 _.partial() 的方式,提供一個開箱即用的上層接口。就像 $.ajax() 和 $.get() 一樣。
_.defer() 和 _.memoize() 可以用在有大運算量的業務場景。
來自:https://www.mxgw.info/t/javascript-cookbook-2nd-function.html