給onScroll減減壓

現在很多功能都需要監聽onscroll事件完成的,然而onscroll觸發得很頻繁也是衆所周知。
測試一下(請先讓瀏覽器出現滾動條

var no = 0;
/**
 * 統計觸發次數
 */
function statistical() {
    console.log( ++no );
}
window.addEventListener('scroll', function() {
    statistical();
});


減壓方式

1.setTimeout、clearTimeout

var no = 0,
    timeout;
/**
 * 統計觸發次數
 */
function statistical() {
    console.log( ++no );
}
window.addEventListener('scroll',function() {
    if ( timeout ) {
        clearTimeout( timeout );
    }
    timeout = setTimeout(function() {
        statistical();
        timeout = null;
    },300);
});

弊端
onScroll觸發多少次,就會執行多少次clearTimeout、setTimeout,
一直滑動滑輪的時候,不會觸發處理函數,因爲onScroll一直在clearTimeout。

2.setInterval

var change = false,
    no = 0;

/**
 * 統計觸發次數
 */
function statistical() {
    console.log( ++no );
}
/**
 * 間隔一定時間判斷滾動座標是否變化,變化則執行處理
 */
setInterval(function() {
    if ( change ) {
        change = false;
        statistical();
    }
},300);

/**
 * 監聽onScroll事件,告訴setInterval,滾動座標已經發生改變
 */
window.addEventListener('scroll',function() {
    change = true;
});

弊端
setInterval一直佔用內存。


總結

setTimeout的方式clearTimeout、setTimeout太頻繁,壓力還是相當大;
而setInterval的方式,setInterval一直佔用內存,但大多數的情況下,它只做判斷,就是判斷change是否爲true。
onscroll真的只做一件事,就是設置change變量爲true,壓力少了很多。
由此可見,setInterval 優於 setTimeout。
但我們總不能一個處理,添加一個onScroll、一個setInterval呢,所以寫成模塊。


模塊代碼

!(function( obj ) {
    //異步執行,有更好的方法,詳見http://blog.csdn.net/zakkye/article/details/45890829
var asynFire = asynFire || function(handle) { setTimeout.call( window, handle ) };

    //eventId前綴,是個隨機字符串,避免一個頁面存在兩個 scrollListener 的時候,eventId相同
var idPf = Math.random().toString(36).substr(2).substr(0,4) + '_',
    //累加Id
    guid = 1,
    //window 當前的scrollLeft
    nX = 0,
    //window 當前的scrollTop
    nY = 0,
    //setInterval Id
    interval = 0,
    //scrollListener 監聽事件所用id
    eventId = '.scrollListener',
    //是否已初始化
    inited = false,
    //setInterval 延遲時間
    lazy = 300,
    //所有回調緩存對象
    callbacks = {},
    //滾動座標是否已改變
    change = false;

/**
 * 初始化,對基本屬性賦值及添加監聽
 */
function init() {
    //已經初始化就不操作
    if ( inited ) return;
    addEvent();
    addInterval();
    inited = true;
}

/**
 * 添加scroll事件監聽
 */
function addEvent() {
    $(window).on('scroll' + eventId,function(e){
        change = true;
    });
}
/**
 * 刪除scroll事件監聽
 */
function removeEvent () {
    $(window).off(eventId);
}
/**
 * 添加循環監聽
 */
function addInterval () {
    interval = setInterval(function(){
        determine();
    }, lazy);
}
/**
 * 刪除循環監聽
 */
function removeInterval () {
    clearInterval( interval );
    interval = null;
}

 /**
 * 判斷當時是否應該執行回調
 */
function determine() {
    var $window,
        _oX,
        _oY,
        _nX,
        _nY;
    if ( change ) {
        change = false;
        $window = $(window);
        _oX = nX;
        _oY = nY;
        _nX = $window.scrollLeft();
        _nY = $window.scrollTop();
        scrollListener.fire( _nX, _nY , _nX > _oX, _nY > _oY );
        nX = _nX;
        nY = _nY;
    }
}

/**
 * 滾動監聽功能(減少onscroll處理壓力)
 * @type {Object}
 */
var scrollListener = {
    /**
     * add 添加處理
     * @param {Function} callback 
     * @return {boolean || number} 添加成功返回對應id,否則返回false
     */
    add : function( callback ) {
        var id,
            $window;
        if ( typeof callback === 'function' ) {
            $window = $( window );
            if ( !inited ) {
                init();
            }
            id = idPf + ( guid++ );
            callbacks[id] = callback;
            //由於會先執行一次,所以必須先返回eventId,再執行,所以用了異步,而且事件監聽本來也屬於異步
            //也存在一些函數,判斷頁面已經滾動到相應區域,執行一次處理後,就要scrollListener.remove了,所以必須異步
            asynFire(function() {
                callback({
                    x : $window.scrollLeft(),
                    y : $window.scrollTop(),
                    right : true,
                    down : true
                })
            });
            return id;
        }
        return false;
    },
    /**
     * 刪除處理
     * @param  {number} id 添加回調時返回的id
     * @return {boolean}    是否刪除成功
     */
    remove : function( id ) {
        if (id && callbacks[id]) {
            delete callbacks[id];
            return true;
        }
        return false;
    },
    /**
     * 執行所有回調
     * @param  {number} x    滾動條x座標
     * @param  {number} y    滾動條y座標
     * @param  {boolean} right 是否爲向右滾動
     * @param  {boolean} down 是否爲向下滾動
     * @return {scrollListener}
     */
    fire : function( x, y, right, down ) {
        var i,
            data = {
                x : typeof x === 'number' ? x : $(window).scrollLeft(),
                y : typeof y === 'number' ? y : $(window).scrollTop(),
                right : typeof right === 'boolean' ? right : true,
                down : typeof down === 'boolean' ? down : true
            };
        for (i in callbacks) {
            callbacks[i]( data );
        }
        return this;
    },
    /**
     * 設置監聽延遲時間
     * @param {number} time 時間戳
     * @return {scrollListener}
     */
    setLazy : function( time ) {
        if ( typeof time === 'number' ) {
            //如果已經初始化,則馬上修改監聽時間
            lazy = time;
            if ( inited ) {
                removeInterval();
                addInterval();
            }
        }
        return this;
    },
    /**
     * 銷燬
     * @return {scrollListener}
     */
    destroy : function () {
        removeEvent()
        removeInterval();
        inited = false;
        callbacks = {};
        return this;
    }
}

obj.scrollListener = scrollListener;
})( window );



測試例子

var id1 = scrollListener.add(function() {
    console.log('scroll1');
});
var id2 = scrollListener.add(function() {
    console.log('scroll2');
});
scrollListener.remove( id2 );
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章