回調函數 在javascript中,當一個函數A作為另外一個函數B的其中一個參數時,則稱A函數為回調函數,即A可以在函數B的運行周期內執行(開始,中間,結束)。 舉例來說,有一個函數用於生成node. 又有一個findNodes函數聲明用於查找所有節點,然後通過callback回調進行執行代碼。 關 ...
回調函數
在javascript中,當一個函數A作為另外一個函數B的其中一個參數時,則稱A函數為回調函數,即A可以在函數B的運行周期內執行(開始,中間,結束)。
舉例來說,有一個函數用於生成node.
var complexComutation = function(){ // 內部處理,並返回一個node }
又有一個findNodes函數聲明用於查找所有節點,然後通過callback回調進行執行代碼。
var findNodes = function(callback){ var nodes = []; var node = complexComputation(); // 如果回調函數可用,則執行她 if(typeof callback === 'function'){ callback(node); } nodes.push(node); return nodes; }
關於callback的定義,我們可以事先定義好來用:
// 定義callback var hide = function(node){ node.style.display = 'node'; } var hiddenNodes = findNodes(hide);
也可以在調用的時候使用匿名定義,如下:
// 使用匿名定義callback var blockNodes = findNodes(function(node){ node.style.display = 'block'; })
我們平時用的最多的,估計就數jQuery的ajax方法的調用了,通過在done/faild上定義callback,以便在ajax調用成功或者失敗的時候做進一步處理,代碼如下(本代碼基於jquery1.8版):
var menId = $('ul.nav').first().attr('id'); var request = $.ajax({ url: 'script.php', type: 'post', data: {id: menuId}, dataType: 'html' }) // 調用成功時的回調處理 request.done(function(msg){ $('#log').html(msg); }) // 調用失敗時的回調處理 request.fail(function(jqXHR, textStatus){ alert('Request failed:' + textStatus); });
配置對象
如果一個函數(或方法)的參數只有一個,並且參數為對象字面量,我們則稱這種模式為配置對象模式。如下所述:
var her = { username: 'gaohan', first: 'gao', last: 'han' }; addPerson(her);
則在addPerson函數內部,就可以隨意使用her對象內的值了,一般用於初始化工作,例如jquery里的ajaxSetup也就是這種方式來實現的:
// 事先設置好初始值 $.ajaxSetup({ url: '/xmlhttp/', global: false, type: 'POST' }) // 然後再調用 $.ajax({ data:myData });
另外,很多jquery的插件也有這種形式的傳參,只不過也可以不傳,不傳的時候則就使用預設值了。
返回函數
返回函數,則是指一個函數的返回值為另一個函數,或者根據特定的條件靈活創建的新函數,示例如下:
var setup = function(){ console.log(1); return function(){ console.log(2); }; }; // 調用setup函數 var her = setup(); // 1 her(); // 2 // 或者直接調用也可以; setup()(); // 2
或者我們可以利用閉包的特性,在函數中加入一個計數器,用來計算函數被調用的次數:
var setup = function(){ var count = 0; return function(){ retunr ++count; }; }; var next = serup(); next(); // 1 next(); // 2 next(); // 3
Currying
Currying是函數式編程的一個特性,將多個參數的處理轉化成單個參數的處理,類似鏈式調用。
下麵來舉一個簡單的例子:
function add(x, y){ var oldx = x, oldy = y; if(typeof oldy === 'undefined'){ return function (newy){ return oldx + newy; } } return x + y ; }
這樣的話,調用方式就有很多種了,比如:
// 測試 typeof add(5); // 'function' add(3)(4); // 7 // 也可以這樣調用 var add2000 = add(2000); add2000(10); // 2010
接下來我們定義一個,比較常用的currying函數:
// 第一個參數為要應用的function, 第二個參數是需要傳入的最少參數個數 function curry(func, minArgs) { if(minArgs == undefined) { minArgs = 1; } function funcWithArgsFrozen(frozenargs){ return function () { // 優化處理,如果調用時沒有參數,返回該函數本身 var ages = Array.prototype.slice.call(arguments); var newArgs = frozenargs.concat(args); if(newArgs.length >= minArgs) { return func.apply(this, newArgs); }else{ return funcWithArgsFrozen(newArgs); } } } return funcWithArgsFrozen([]); }
這樣,我們就可以隨意定義我們的業務行為了,比如定義加法:
var plus = curry(function(){ var result = 0; for(var i=0; i<arguments.length; ++i){ result += arguments[i]; } return result; }, 2)
使用方法多種多樣。
plus(3, 2) // 正常調用 plus(3) // 偏應用 plus(3) (2) // 完整應用(返回5) plus() (3) () () (2) // 返回5 plus(3, 2, 4, 5) // 可以接受多個參數 plus(3) (2, 3, 5) // 同理
JavaScript里的Function有很多特殊的功效,可以利用閉包以及arguments參數特性實現很多不同的技巧,下一篇我們將繼續介紹利用Function進行初始化的技巧。
立即執行的函數
// 聲明完函數以後,立即執行該函數 (function (){ console.log('watch out'); }()); // 這種方式聲明的函數,也可以立即執行 !function(){ console.log('watch out!'); }(); // 如下的方式也都可以哦 ~function(){ /* do someing*/ }(); -function(){ /* do someing*/ }(); +function(){ /* do someing*/ }();
立即執行的對象初始化
該模式的意思是指在聲明一個對象(並非函數)的時候、立即執行對象里的某一個方法進行初始化工作,通常該模式可以用在執行一次性的代碼上:
({ // 這裡你可以定義常量,並設置其它值 maxwidth: 600, maxheight: 400, // 當然也可以定義方法 gimmMax: function(){ return this.maxwidth + 'x' + this.maxheight; }, init: function(){ console.log(this.gimmeMax()); // 更多代碼...... } }).init(); // 這樣就能初始化嘍
分支初始化
分支初始化是指在初始化的時候,根據不同的條件(場景)初始化不同的代碼,也就是所謂的條件語句賦值。之前我們在處理的時候,常常使用類似下麵的代碼:
var utils = { addListener: function(el, type, fn){ if(typeof window.addEventListener === 'function'){ el.addEventListener(type, fn, false); }else if(typeof document.attachEvent !== 'undefined'){ el.attachEvent('on' + type, fn); }else{ el['on' + type] = fn; } }, removeListener: function(el, type, fn){ // 神馬之類的...... } }
我們來改進一下,首先我們要定義兩個介面,一個用來add事件句柄,一個用來remove事件句柄,代碼如下:
var utils = { addListener: null, removeListener: null };
實現代碼如下:
if (typeof window.addEventListener === 'function') { utils.addListener = function (el, type, fn) { el.addEventListener(type, fn, false); }; } else if (typeof document.attachEvent !== 'undefined') { // 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; }; }
用起來,是不是就很方便了?代碼也優雅多了。
自聲明函數
一般是在函數內部,重寫重名函數代碼,比如:
var her = function(){ alert('Boo!'); her = function(){ alert('Double Boo!!'); } }
這種代碼,非常容易使人迷惑,我們先來看看例子的執行結果:
// 1.添加新屬性 scareMe.prototype = 'Anna'; // 2. scareMe賦予一個新值 var prank = scareMe; // 3. 作為一個方法調用 var spooky = { boo: scareMe } // 使用新變數名稱進行調用 prank(); // 'Boo!' prank(); // 'Boo!' console.log(spooky.boo.property); // 'properly'
通過執行結果,可以發現,將定於的函數賦值與新變數(或內部方法),代碼並不執行重載的scareMe代碼,而如下例子則正好相反:
// 使用自聲明函數 scareMe(); // Double boo! scareMe(); // Double boo! console.log(scareMe.property); // undefined
大家使用這種模式時,一定要非常小心才行,否則實際結果很可能和你期望的結果不一樣,當然你也可以利用這個特殊做一些特殊的操作。
記憶體優化
該模式主要是利用函數的屬性特性來避免大量的重覆計算。通常代碼形式如下:
var myFunc = function (param) { if (!myFunc.cache[param]) { var result = {}; // ... 複雜操作 ... myFunc.cache[param] = result; } return myFunc.cache[param]; }; // cache 存儲 myFunc.cache = {};
但是上述代碼有個問題,如果傳入的參數是toString或者其它類似Object擁有的一些公用方法的話,就會出現問題,這時候就需要使用傳說中的hasOwnProperty
方法了,代碼如下:
var myFunc = function (param) { if (!myFunc.cache.hasOwnProperty(param)) { var result = {}; // ... 複雜操作 ... myFunc.cache[param] = result; } return myFunc.cache[param]; }; // cache 存儲 myFunc.cache = {};
或者如果你傳入的參數是多個的話,可以將這些參數通過JSON的stringify方法生產一個cachekey值進行存儲,代碼如下:
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]; }; // cache 存儲 myFunc.cache = {};
或者多個參數的話,也可以利用arguments.callee特性:
var myFunc = function (param) { var f = arguments.callee, result; if (!f.cache[param]) { result = {}; // ... 複雜操作 ... f.cache[param] = result; } return f.cache[param]; }; // cache 存儲 myFunc.cache = {}; 總結