《JavaScript 模式》讀書筆記(4)— 函數4
- 2020 年 3 月 28 日
- 筆記
這篇文章我們主要來學習下即時對象初始化、初始化時分支、函數屬性-備忘模式以及配置對象。這篇的內容會有點多。
六、即時對象初始化
保護全局作用域不受污染的另一種方法,即時對象初始化模式。這種模式使用帶有init()方法的對象,該方法在創建對象後將會立即執行。init()函數需要負責所有的初始化任務。
({ // 在這裡可以定義設定值 // 又名配置常數 maxwidth:600, maxheight:400, // 還可以定義一些實用的方法 gimmeMax:function () { return this.maxwidth + "x" + this.maxheight; }, // 初始化 init:function() { console.log(this.gimmeMax()); // 更多初始化任務 } }).init()
就語法而言,對待這種模式就像在使用對象字面量創建一個普通的對象。也可以將字面量包裝到括號中(分組操作符),它指示JavaScript引擎將大括號作為對象字面量,而不是作為一個代碼塊(也不是if或者for循環)。在該括號結束之後,可以立即調用init()方法。
這兩種方法都可以運行:
({...}).init(); ({...}.init());
這種方法可以在執行一次性初始化任務時,保護全局命名空間。
與僅僅將一堆代碼包裝到匿名函數的方法相比,這種模式看起來涉及更多的語法特徵,但是如果初始化任務更加複雜,它會使整個初始化過程顯得更有結構化。比如,私有幫助函數是非常清晰可辯別的,因為他們是臨時對象的屬性,而在即時函數模式中,他們就很可能只是分散在各處的函數而已。
這種模式主要適用於一次性的任務,而且在init()完畢後也沒有對該對象的訪問,如果想要在init()完畢後保存對該對象的一個引用,可以通過在init()尾部添加”return this;”語句實現該功能。
var a = ({ // 在這裡可以定義設定值 // 又名配置常數 maxwidth:600, maxheight:400, // 還可以定義一些實用的方法 gimmeMax:function () { return this.maxwidth + "x" + this.maxheight; }, // 初始化 init:function() { console.log(this.gimmeMax()); // 更多初始化任務 return this; } }).init() console.log(a.maxheight)
七、初始化時分支
初始化時分支(Init-time branching,也稱為加載時分支Load-time branching)是一種優化模式。當知道某個條件在整個程序聲明周期內都不會發生改變的時候,僅對該條件測試一次時很有意義的。瀏覽器嗅探就是一個典型的例子。
查明DOM元素的計算樣式或附加的事件處理程序是另外一個可以受益於初始化時分支模式的場景。絕大多數程序開發員都已經編寫過這樣的代碼,至少有一次在他們的客戶端編程生命周期內,既可用於附加或刪除事件監聽器的工具:
// 之前 var utils = { addListener: function(el,type,fn) { if(typeof window.addEventListener === 'function') { el.addEventListener(type,fn,false); } else if(typeof document.attachEvent === 'function') { //'IE' el.attachEvent('on' + type,fn); } else { el['on' + type] = fn; } }, removeListener:function(el,type,fn){ // 幾乎一樣 } }
此段代碼效率低下。每次在調用utils.addListener()或utils.removeListener()時,都會重複執行相同的檢查。
當使用初始化分支的時候,可以在腳本初始化加載時一次性探測出瀏覽器特徵。此時,可以在整個頁面生命周期內重定義函數運行方式:
// 之後 var utils = { addListener:null, removeListener:null }; //實現 if(typeof window.addEventListener === 'function') { utils.addListener = function(el,type,fn){ el.addEventListener(type,fn,false); }; utils.removeListener = function(el,type,fn){ el.removeEventListener(type,fn,false); }; } else if(typeof document.attachEvent === 'function') { //判斷為IE瀏覽器 utils.addListener = function(el,type,fn){ el.attachEvent('on' + type,fn); }; utils.removeListener = function(el,type,fn){ el.detachEvent('on' + type,fn); }; } else { //更早的版本 utils.addListener = function(el,type,fn){ el['on' + type] = fn; }; utils.removeListener = function(el,type,fn){ el['on' + type] = null; }; }
其實,簡單來說,個人理解,分支初始化的意義就在於:把重複的事情僅做一次。
八、函數屬性—備忘模式
函數是對象,因此它們具有屬性。事實上,它們確實還有屬性和方法。比如,對於每一個函數,無論使用什麼樣的語法來創建它,它都會自動獲得一個length屬性,其中包含了該函數期望的參數數量。
function func(a, b, c) {} console.log(func.length); //3
可以在任何時候將自定義屬性添加到你的函數中。自定義屬性的其中一個用例是緩存函數結果(即返回值),因此,在下一次調用該函數時就不用重做潛在的繁重計算。緩存函數結果,也被稱為備忘。
var myFunc = function (param) { if(!myFunc.cache[param]) { var result = {}; // ... 開銷很大的操作 ... myFunc.cache[param] = result; } return myFunc.cache[param]; }; // 緩存存儲 myFunc.cache = {};
上面的例子中,函數myFunc創建了一個屬性cache,該屬性可以通過myFunc.cache像通常那樣進行訪問。cache屬性是一個對象,其中使用傳遞給函數的參數param作為鍵,而計算結果作為值。計算結果可以是需要的任意複雜數據結構。
上面的代碼假定該函數只需要一個參數param,並且它是一個基本數據類型。如果有更多以及更複雜的參數,對此的通用解決方案是將它們序列化。即,可以將參數對象序列化為一個JSON字符串,並使用該字符串作為cache對象的鍵:
var myFunc = function () { var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)), result; if(!myFunc.cache[cachekey]) { result = {}; // ... 開銷很大的操作 ... myFunc.cache[cachekey] = result; } return myFunc.cache[cachekey]; }; // 緩存存儲 myFunc.cache = {};
請注意,在序列化的過程中,對象的“標識”將會丟失。如果有兩個不同的對象並且恰好都具有相同的屬性,這兩個對象將會共享同一個緩存條目。
編寫前面的函數的另一種方法是使用arguments.callee來引用該函數,而不是使用硬編碼函數名稱。雖然在目前這是可行的,但是在ES5的嚴格模式中並不支持arguments.callee。
var myFunc = function (param) { var f = arguments.callee, result; if(!f.cache[param]) { result = {}; // ... 開銷很大的操作 ... f.cache[param] = result; } return f.cache[param]; }; // 緩存存儲 myFunc.cache = {};
九、配置對象
配置對象模式(configuration object pattern)是一種提供更簡潔API的方法,尤其是在建立一個庫或任何將被其他程序使用的代碼的情況。
其實配置對象的概念很簡單,或許你已經在開發工作中使用了:
// 由於軟件的開發是不斷推進的,需求必然會頻繁變更。 // 假設,我們正在編寫一個addPerson()的函數,該函數接受人員的名和姓作為參數。 function addPerson(first,last){ //... }; // 後來,又要加入人員的性別,住址,電話啥的,住址和電話是可選的 // 這裡把可選的參數放在末尾 function addPerson(first,last,gender,address,phone){ //... }; // 然後,又需要直到用戶名,註冊的日期等,這是必填的。 // 於是,就是這樣了 function addPerson(first,last,gender,address,phone,username,addDate){ //... }; // 這時候,可選的參數,也必須要傳遞了 addPerson('first','last','gender',null,null,'username','addDate'); // 這個參數列表變得越來越長,越來越無法維護
所以,我們可以僅使用一個參數來代表所有參數,讓我們將該參數對象成為conf,即“配置”的意思。
addPerson(conf);
然後,該函數的使用者可以這樣做:
var conf = { username:'zaking', first:"Bruce", last:"Wayne" }; addPerson(conf);
配置對象的優點在於:
- 不需要記住眾多的參數以及其順序。
- 可以安全忽略可選參數。
- 更加易於閱讀和維護。
- 更加易於添加和刪除參數。
缺點是這樣的:
- 需要記住參數名稱。
- 屬性名稱無法被壓縮。
當函數創建DOM元素時,這種模式可能是非常有用的,例如,可以用在設置元素的CSS樣式中,因為元素和樣式可能具有大量的可選特徵和屬性。
這篇文章的內容就到這裡了,都是一些小的point,但是其實也很有必要去學習一下,下一章將會是函數部分的最後一章,我們會聊一聊“Curry化”。