JS在項目中用到的AOP, 以及函數節流, 防抖, 事件總線

1. 項目中在綁定事件的時候總想在觸發前,或者觸發後作一些統一的判斷或邏輯,在c#後端代碼裏,能夠用Attribute, filter等標籤特性實現AOP的效果,但是js中沒有這種用法,歸根到本質仍是不支持類型的攔截和判斷,因此無法實現,可是js的靈活就在於能夠經過原型鏈, 高階函數,閉包等特性來實現相似的效果,這裏記錄一下便於複習c#

    //AOP: after
    Function.prototype.after = function (afterFn) {
        var _self = this;
        return function () {
            var ret = _self.apply(this, arguments);
            if (ret === false) {
                return false;
            }
            afterFn.apply(this, arguments);
            return ret;
        }
    }


    //AOP: before
    Function.prototype.before = function (beforeFn) {
        var _self = this;
        return function () {
            var ret = beforeFn.apply(this, arguments);
            if (ret === false) {
                return false;
            }
            return _self.apply(this, arguments);
        }
    }

2. 用法下面的例子是工程中實用, 在tab點擊的時候,通常狀況沒打開一個tab要綁定一個或多個事件作點擊,關閉等處理,這裏實現的方法就是在父元素統一隻綁定一個事件,都經過冒泡來觸發,這種好處就是新加tab不用綁定事件,很差的就是可能tab層級複雜,須要作不少元素的判斷來決定觸發什麼事件,好比說下面的關閉事件裏面先判斷後,要是沒有觸發,在判斷觸發點擊事件,這樣寫的邏輯清晰,功能模塊能夠分開,利於解耦,效果也能很好實現!!!後端

                _bindEvents: function () {
                    var g = this,
                        p = this.options,
                        container = g.tab.bar.container;

                    // 標籤點擊事件, 統一隻綁定父元素一次
                    container.nav.bind("click.tab.nav", function (e) {
                        if (e.target === e.currentTarget) {
                            e.stopPropagation();
                            return;
                        }
                        g._tabCloseClick.after(g._tabBarClick).call(g, e);
                    });
                    
                  }    

3. 固然還有函數的防抖和節流的實現:閉包

函數防抖就是爲了讓一個函數在必定的時間只執行一次,即便被屢次觸發,典型應用就是tab標籤打開不少的時候,不停的拖動窗體大小或者改變位置,這樣標籤會有一些動畫效果用來自適應窗體大小,可是要是頻發的拖動窗體,這些動畫效果就會被觸發屢次,致使拖完了後一段時間內,這種效果不停在重複,延遲嚴重,因此用debounce就能夠在必定時間後觸發一次效果就能夠了,性能提高的同時,體驗也很好app

函數節流 就是規定一個單位時間,在這個單位時間內,只能有一次觸發事件的回調函數執行,若是在同一個單位時間內某事件被觸發屢次,只有一次能生效,在項目中應用的例子就是在手風琴點擊展開層級的時候,若是用戶在短期內重複快速點擊屢次,這樣這個層級就會不停的觸發屢次,展開收起又展開,感受發瘋同樣,因此這時候用throttle節流一下, 好比在300ms內就觸發一次,無論你點了多少次,這樣體驗大大提升函數

    /**
    * 函數防抖, 在事件被觸發n秒後再執行回調,若是在這n秒內又被觸發,則從新計時
    * @param fn {Function}   實際要執行的函數
    * @param delay {Number}  延遲時間,也就是閾值,單位是毫秒(ms)
    * @return {Function}     返回一個防抖了的函數
    */
    vango.utils.debounce = function (fn, delay) {
        var timer;
        return function () {
            var context = this;
            var args = arguments;
            clearTimeout(timer);
            timer = setTimeout(function () {
                fn.apply(context, args);
            }, delay);
        }
    }

    /**
    * 函數節流,規定一個單位時間,在這個單位時間內,只能有一次觸發事件的回調函數執行,若是在同一個單位時間內某事件被觸發屢次,只有一次能生效
    * @param fn {Function}   實際要執行的函數
    * @param wait {Number}  延遲時間,也就是閾值,單位是毫秒(ms)
    * @return {Function}     返回一個節流了的函數
    */
    vango.utils.throttle = function (func, wait, options) {
        /* options的默認值
         *  表示首次調用返回值方法時,會立刻調用func;不然僅會記錄當前時刻,當第二次調用的時間間隔超過wait時,才調用func。
         *  options.leading = true;
         * 表示當調用方法時,未到達wait指定的時間間隔,則啓動計時器延遲調用func函數,若後續在既未達到wait指定的時間間隔和func函數又未被調用的狀況下調用返回值方法,則被調用請求將被丟棄。
         *  options.trailing = true; 
         * 注意:當options.trailing = false時,效果與上面的簡單實現效果相同
         */
        var context, args, result;
        var timeout = null;
        var previous = 0;
        if (!options) options = {};
        var later = function () {
            previous = options.leading === false ? 0 : new Date();;
            timeout = null;
            result = func.apply(context, args);
            if (!timeout) context = args = null;
        };
        return function () {
            var now = new Date();;
            if (!previous && options.leading === false) previous = now;
            // 計算剩餘時間
            var remaining = wait - (now - previous);
            context = this;
            args = arguments;
            // 當到達wait指定的時間間隔,則調用func函數
            // 精彩之處:按理來講remaining <= 0已經足夠證實已經到達wait的時間間隔,但這裏還考慮到假如客戶端修改了系統時間則立刻執行func函數。
            if (remaining <= 0 || remaining > wait) {
                // 因爲setTimeout存在最小時間精度問題,所以會存在到達wait的時間間隔,但以前設置的setTimeout操做還沒被執行,所以爲保險起見,這裏先清理setTimeout操做
                if (timeout) {
                    clearTimeout(timeout);
                    timeout = null;
                }
                previous = now;
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            } else if (!timeout && options.trailing !== false) {
                // options.trailing=true時,延時執行func函數
                timeout = setTimeout(later, remaining);
            }
            return result;
        };
    };

 5. 對於一個系統來講事件的設計固然必不可少,平臺中簡單的實現了一下事件總線的機制,記錄一下性能

    vango.event = (function () {

        var _callbacks = {};

        var on = function (eventName, callback) {
            if (!_callbacks[eventName]) {
                _callbacks[eventName] = [];
            }

            _callbacks[eventName].push(callback);
        };

        var off = function (eventName, callback) {
            var callbacks = _callbacks[eventName];
            if (!callbacks) {
                return;
            }

            var index = -1;
            for (var i = 0; i < callbacks.length; i++) {
                if (callbacks[i] === callback) {
                    index = i;
                    break;
                }
            }

            if (index < 0) {
                return;
            }

            _callbacks[eventName].splice(index, 1);
        };

        var trigger = function (eventName) {
            var callbacks = _callbacks[eventName];
            if (!callbacks || !callbacks.length) {
                return;
            }

            var args = Array.prototype.slice.call(arguments, 1);
            for (var i = 0; i < callbacks.length; i++) {
                callbacks[i].apply(this, args);
            }
        };

        return {
            on: on,
            off: off,
            trigger: trigger
        };
    })();
相關文章
相關標籤/搜索