给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 );
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章