在通常的編程語言中,函數的參數只能是基本類型或者對象引用,返回值也只是基本數據類型或對象引用。但在Javascript中函數作為一等公民,既可以當做參數傳遞,也可以被當做返回值返回。所謂高階函數就是可以把函數作為參數,或者是將函數作為返回值的函數。這兩種情形在實際開發中有很多應用場景,本文是我在工作學習中遇到的幾種應用場景的總結。
回調函數
代碼復用是衡量一個應用程序的重要標准之一。通過將變化的業務邏輯抽離封裝在回調函數中能夠有效的提高代碼復用率。比如ES5中為數組增加的forEach方法,遍歷數組,對每個元素調用同一個函數。
array = {}; array.forEach = function(arr, fn){ for (var i = 0, len = arr.length; i < len; i++) { fn(arr[i], i, arr); } }
通過回調函數將業務的重點聚焦在回調函數中,而不必每次都要重復編寫遍歷代碼。
偏函數
作為將函數當做返回值輸出的典型應用就是偏函數。所謂偏函數是指創建一個調用另外一個部分——參數或變量已經預置的函數——的函數的用法。反正看著定義我是沒理解這東東干嘛的。咱們還是先看例子吧,偏函數最典型的例子就是類型判斷。
Javascript對象都擁有三個屬性:原型屬性、類屬性、可擴展性。(不知道的同學要回去翻犀牛書哦,page:138)類屬性是一個字符串,Javascript中並未直接提供,但我們可以利用Object.prototype.toString來間接得到。該函數總是返回如下形式:
[object Class]
因此我們可以編寫一系列isType函數。
代碼如下:
isString = function(obj){ return Object.prototype.toString.call(obj) === "[object String]"; } isNumber = function(obj){ return Object.prototype.toString.call(obj) === "[object Number]"; } isArray = function(obj){ return Object.prototype.toString.call(obj) === "[object Array]"; }
這幾個函數中大部分代碼是重復的,這時高階函數便華麗麗的登場了:
isType = function(type) { return function(obj) { return Object.prototype.toString.call(obj) === "[object " + type + "]"; } } isString = isType('String'); isNumber = isType('Number'); isArray = isType('Array');
所以通過指定部分參數來返回一個新的定制函數的形式就是偏函數。
currying(柯裡化)
currying又稱部分求值。一個currying的函數首先會接受一些參數,接受這些參數之後,函數並不會立即求值,而是繼續返回另一個函數,剛才傳入的參數在函數形成的閉包中被保存起來。待到函數被真正需要求值的時候,之前傳入的所有參數都會被一次性用於求值。
var currying = function(fn) { var args = []; return function() { if (arguments.length === 0) { return fn.applay(this, args); } else { args = args.concat(arguments); return arguments.callee; } } }
假設我們以計算一個月每天花銷為例:
var currying = function(fn) { debugger; var args = []; return function() { if (arguments.length === 0) { return fn.apply(this, args); } else { Array.prototype.push.apply(args, arguments); return arguments.callee; } } } cost = function(){ var sum = 0; for (var i = 0, len = arguments.length; i < len; i++) { sum += arguments[i]; } return sum; } var cost = currying(cost); cost(100); cost(200); alert(cost())
事件節流
在某些場景下,某些事件可能會被重復的觸發,但事件處理函數並不需要每次都執行。比如在window.resize事件中進行復雜的邏輯計算,如果用戶頻繁的改變浏覽器大小,復雜計算會對性能造成嚴重影響;有時這些邏輯計算並不需要每次rezise時都觸發,只需要計算有限的幾次便可以。這時我們需要根據時間段來忽略一些事件請求。請看以下節流函數:
function throttle(fn, interval) { var doing = false; return function() { if (doing) { return; } doing = true; fn.apply(this, arguments); setTimeout(function() { doing = false; }, interval); } } window.onresize = throttle(function(){ console.log('execute'); }, 500);
通過控制函數執行時間,可以在函數執行次數與功能需求之間達到完美平衡。另一個事件是mousemove。如果我們給一個dom元素綁定該事件,鼠標在改元素上移動時,該事件便會重復觸發。
事件結束
對於某些可以頻繁觸發的事件,有時候我們希望在事件結束後進行一系列操作。這時我們可以利用高階函數做如下處理:
function debounce(fn, interval) { var timer = null; function delay() { var target = this; var args = arguments; return setTimeout(function(){ fn.apply(target, args); }, interval); } return function() { if (timer) { clearTimeout(timer); } timer = delay.apply(this, arguments); } }; window.onresize = throttle(function(){ console.log('resize end'); }, 500);
如果在這一過程中事件被觸發則清除上一次事件句柄,重新綁定執行時間。
參考資料:
《深入淺出node》
《Javascript設計模式與開發實踐》