JavaScript高階函數的應用

定義

高階函數是指至少知足下列條件之一的函數:javascript

  • 函數能夠做爲參數被傳遞;java

  • 函數能夠做爲返回值輸出。ajax

JavaScript語言中的函數顯然知足高階函數的條件,在實際開發中,不管是將函數看成參數傳遞,仍是讓函數的執行結果返回另一個函數,這兩種情形都有不少應用場景,如下就是一些高階函數的應用。編程

應用

做爲參數傳遞

ajax異步請求

// callback爲待傳入的回調函數
var getUserInfo = function(userId, callback) {
     $.ajax("http://xxx.com/getUserInfo?" + userId, function(data) {
        if (typeof callback === "function") {
            callback(data);
        }
    });
}

getUserInfo(13157, function(data) {
    alert (data.userName);
});

Array.prototype.sort

Array.prototype.sort接受一個函數看成參數,這個函數裏面封裝了數組元素的排序規則。從Array.prototype.sort的使用能夠看到,咱們的目的是對數組進行排序,這是不變的部分;而使用什麼規則去排序,則是可變的部分。把可變的部分封裝在函數參數裏,動態傳入Array.prototype.sort,使Array.prototype.sort方法成爲了一個很是靈活的方法。數組

//從小到大排列
[1, 4, 3].sort(function(a, b) {
    return a - b;
});
// 輸出: [1, 3, 4]

//從大到小排列
[1, 4, 3].sort(function(a, b) {
    return b - a;
});
// 輸出: [4, 3, 1]

做爲返回值

判斷數據的類型

var Type = {};

for (var i = 0, type; type = ['String', 'Array', 'Number'][i++];) {
    (function(type) {
        Type['is' + type] = function(obj) {
            return Object.prototype.toString.call(obj) === '[object '+ type +']';
           }
       })(type)
};

Type.isArray([]);     // 輸出:true
Type.isString("str");     // 輸出:true

單例模式

var getSingle = function(fn) {
    var ret;
    return function() {
        return ret || (ret = fn.apply(this, arguments));
    };
};

實現AOP

AOP(面向切面編程)的主要做用是把一些跟核心業務邏輯模塊無關的功能抽離出來,這些跟業務邏輯無關的功能一般包括日誌統計、安全控制、異常處理等。把這些功能抽離出來以後,再經過「動態織入」的方式摻入業務邏輯模塊中。這樣作的好處首先是能夠保持業務邏輯模塊的純淨和高內聚性,其次是能夠很方便地複用日誌統計等功能模塊。瀏覽器

一般,在JavaScript中實現AOP,都是指把一個函數「動態織入」到另一個函數之中,具體的實現技術有不少,下面的例子經過擴展Function.prototype來作到這一點。安全

Function.prototype.before = function(beforefn) {
    var __self = this;    // 保存原函數的引用
    return function() {    // 返回包含了原函數和新函數的"代理"函數
        beforefn.apply(this, arguments);     // 執行新函數,修正this
        return __self.apply(this, arguments);    // 執行原函數
    }
};

Function.prototype.after = function(afterfn) {
    var __self = this;
    return function() {
        var ret = __self.apply(this, arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
};

var func = function() {
    console.log(2);
};

func = func.before(function() {
    console.log(1);
}).after(function() {
    console.log(3);
});

func();

// 按順序打印出1,2,3

currying

currying(函數柯里化),又稱部分求值。一個currying的函數首先會接受一些參數,接受了這些參數以後,該函數並不會當即求值,而是繼續返回另一個函數,剛纔傳入的參數在函數造成的閉包中被保存起來。待到函數被真正須要求值的時候,以前傳入的全部參數都會被一次性用於求值。閉包

// 通用currying函數,接受一個參數,即將要被currying的函數
var currying = function(fn) {
    var args = [];
    return function() {
        if (arguments.length === 0) {
            return fn.apply(this, args);
        } else {
            [].push.apply(args, arguments);
            return arguments.callee;
        }
    }
};

// 將被currying的函數
var cost = (function() {
    var money = 0;
    return function() {
        for (var i = 0, l = arguments.length; i < l; i++) {
            money += arguments[i];
        }
        return money;
    }
})();

var cost = currying( cost );    // 轉化成currying函數

cost( 100 );    // 未真正求值
cost( 200 );    // 未真正求值
cost( 300 );    // 未真正求值

console.log (cost());     // 求值並輸出:600

uncurrying

在JavaScript中,當咱們調用對象的某個方法時,其實不用去關心該對象本來是否被設計爲擁有這個方法,這是動態類型語言的特色,也是常說的鴨子類型思想。app

同理,一個對象也未必只能使用它自身的方法,那麼有什麼辦法可讓對象去借用一個本來不屬於它的方法呢?答案對於咱們來講很簡單,call和apply均可以完成這個需求,由於用call和apply能夠把任意對象看成this傳入某個方法,這樣一來,方法中用到this的地方就再也不侷限於原來規定的對象,而是加以泛化並獲得更廣的適用性。異步

而uncurrying的目的是將泛化this的過程提取出來,將fn.call或者fn.apply抽象成通用的函數。

// uncurrying實現
Function.prototype.uncurrying = function() {
    var self = this;
    return function() {
        return Function.prototype.call.apply(self, arguments);
    }
};

// 將Array.prototype.push進行uncurrying,此時push函數的做用就跟Array.prototype.push同樣了,且不只僅侷限於只能操做array對象。
var push = Array.prototype.push.uncurrying();

var obj = {
    "length": 1,
    "0": 1
};

push(obj, 2);
console.log(obj);   // 輸出:{0: 1, 1: 2, length: 2}

函數節流

當一個函數被頻繁調用時,若是會形成很大的性能問題的時候,這個時候能夠考慮函數節流,下降函數被調用的頻率。

throttle函數的原理是,將即將被執行的函數用setTimeout延遲一段時間執行。若是該次延遲執行尚未完成,則忽略接下來調用該函數的請求。throttle函數接受2個參數,第一個參數爲須要被延遲執行的函數,第二個參數爲延遲執行的時間。

var throttle = function(fn, interval) {
    var __self = fn,    // 保存須要被延遲執行的函數引用
        timer,      // 定時器
        firstTime = true;    // 是不是第一次調用

    return function() {
        var args = arguments,
            __me = this;

        if (firstTime) {    // 若是是第一次調用,不需延遲執行
            __self.apply(__me, args);
            return firstTime = false;
        }

        if (timer) {    // 若是定時器還在,說明前一次延遲執行尚未完成
            return false;
        }

        timer = setTimeout(function() {  // 延遲一段時間執行
            clearTimeout(timer);
            timer = null;
            __self.apply(__me, args);
        }, interval || 500 );
    };
};

window.onresize = throttle(function() {
    console.log(1);
}, 500 );

分時函數

當一次的用戶操做會嚴重地影響頁面性能,如在短期內往頁面中大量添加DOM節點顯然也會讓瀏覽器吃不消,咱們看到的結果每每就是瀏覽器的卡頓甚至假死。

這個問題的解決方案之一是下面的timeChunk函數,timeChunk函數讓建立節點的工做分批進行,好比把1秒鐘建立1000個節點,改成每隔200毫秒建立8個節點。

timeChunk函數接受3個參數,第1個參數是建立節點時須要用到的數據,第2個參數是封裝了建立節點邏輯的函數,第3個參數表示每一批建立的節點數量。

var timeChunk = function(ary, fn, count) {
    var t;

    var start = function() {
        for ( var i = 0; i < Math.min( count || 1, ary.length ); i++ ){
            var obj = ary.shift();
            fn( obj );
        }
     };

     return function() {
        t = setInterval(function() {
          if (ary.length === 0) {  // 若是所有節點都已經被建立好
              return clearInterval(t);
          }
          start();
        }, 200);    // 分批執行的時間間隔,也能夠用參數的形式傳入
    };
};

惰性加載函數

在Web開發中,由於瀏覽器之間的實現差別,一些嗅探工做老是不可避免。好比咱們須要一個在各個瀏覽器中可以通用的事件綁定函數addEvent,常見的寫法以下:

方案一:

var addEvent = function(elem, type, handler) {
    if (window.addEventListener) {
       return elem.addEventListener(type, handler, false);
    }
    
    if (window.attachEvent) {
          return elem.attachEvent('on' + type, handler);
    }
};

缺點:當它每次被調用的時候都會執行裏面的if條件分支,雖然執行這些if分支的開銷不算大,但也許有一些方法可讓程序避免這些重複的執行過程。

方案二:

var addEvent = (function() {
    if (window.addEventListener) {
        return function(elem, type, handler) {
            elem.addEventListener(type, handler, false);
        }
    }
    if (window.attachEvent) {
        return function(elem, type, handler) {
            elem.attachEvent('on' + type, handler);
        }
    }
})();

缺點:也許咱們從頭至尾都沒有使用過addEvent函數,這樣看來,一開始的瀏覽器嗅探就是徹底多餘的操做,並且這也會稍稍延長頁面ready的時間。

方案三:

var addEvent = function(elem, type, handler) {
        if (window.addEventListener) {
           addEvent = function(elem, type, handler) {
               elem.addEventListener(type, handler, false);
           }
        } else if (window.attachEvent) {
            addEvent = function(elem, type, handler) {
                elem.attachEvent('on' + type, handler);
            }
        }
        addEvent(elem, type, handler);
    };

此時addEvent依然被聲明爲一個普通函數,在函數裏依然有一些分支判斷。可是在第一次進入條件分支以後,在函數內部會重寫這個函數,重寫以後的函數就是咱們指望的addEvent函數,在下一次進入addEvent函數的時候,addEvent函數裏再也不存在條件分支語句。

相關文章
相關標籤/搜索