Javascript 是你的高階函數(進階應用程式),javascript高階
在通常的程式設計語言中,函數的參數只能是基本類型或者對象引用,傳回值也只是基礎資料型別 (Elementary Data Type)或對象引用。但在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設計模式與開發實踐》