“如何稀释scroll事件”的思考(不小心写了个异步do...while)

看了下园友的一帖子:http://www.cnblogs.com/xzhang/p/4145697.html#commentform

本来以为是很简单的问题,但仔细想想还挺有意思的。简单的说就是增加事件触发的间隔时间。

比如在浏览器的事件是1毫秒调用一次,转换成100毫秒调用一次。

看了下原贴的两方法,觉得可以乐观锁的方法再写个,虽然最后对比结果和typeahead差不多。但过程还是挺有意思的,分享下思路

首先,浏览器事件间隔是没法改变的。所以我们只能改变回调函数的执行间隔。

乐观锁机制的流程是:

            do {
                var _oldVal = _nowVal//记录基准值
                //执行任务
            } while (_oldVal != _nowVal)//比较 检查_nowVlau是否在执行任务的时候发生变化

  

根据这个结构,基本的设计思路就是:

  触发事件时启动一个“任务线程”,执行完指定的任务后,隔一段时间去检查没有没有发生后续事件(通过_oldVal与_nowVal判断),如果有重新执行任务,没有结束线程。

但有一个问题,“任务线程”同一时间只能执行一个,所以在触发事件的时候要做下判断。修改后的程序

    var _taskRun = false, _nowVal = 0, _oldVal = 0;
    function eventFun() {
        if (_taskRun) {
            _nowVal++;
        }
        else {
            _taskRun = true;
            do {
                var _oldVal = _nowVal//记录基准值
                //执行任务
            } while (_oldVal != _nowVal)//比较 检查_nowVlau是否在执行任务的时候发生变化
            _taskRun = false;//执行结束重置状态变量
            _nowVal = 0;
        }
    }

一切很顺利,但是要有间隔啊!js里可没有Thread.sleep,但有setTimeout,所以可以用setTimeout模拟sleep  

SO,Think.....了一会,不小心写了个异步do...while

var _taskRun = false, _nowVal = 0, _oldVal = 0, _time = 100;
        var _do = function (waitTime, funTsk) {//模拟do{}while(true);
            var _endInner, _whileInner;
            _whileInner = function (funcondition) {
                _endInner = function (funEnd) {
                    var _funWhile = function () {
                        if (funcondition()) {
                            _endInner(funEnd);
                        } else {
                            funEnd();
                        }
                    };

                    var _runInner = function () {
                        funTsk();
                        setTimeout(_funWhile, waitTime);//延迟一段时间做判断
                    };
                    _runInner();
                };
                return {
                    "end": _endInner
                };
            };
            return { "while": _whileInner };
        };


        function eventFun() {
            if (_taskRun) {
                _nowVal++;
            }
            else {
                _taskRun = true;

                _do(
                    100,//间隔时间
                     function () {
                         _oldVal = _nowVal//记录基准值
                         console.log(_oldVal);
                     }
                )
                .while(
                    function () {
                        return _oldVal != _nowVal
                    }
                )
                .end(
                    function () {
                        _taskRun = false;//执行结束重置状态变量
                        _nowVal = 0;
                    }
                );
            }
        }

  

现在,基本OK了,但做了下测试,发觉间隔时间没有typeahead的准,怎么回事?

研究了下他的代码。发现高手的思路就不不一样。原来他是用当前时间去计算setTimeout的调用间隔的。SO出来的结果更加准确。

比如,设置间隔100秒,一个事件在距上个事件50秒的时候发生,为了保证每次100秒的间隔,这个事件的setTimeOut时间就应该设置成50秒而不是100秒

根据这个思路再修改了下代码,给出完整版:

    var _asynFun = function (func, wait) {
        var context, args, result, _taskRun = false, _nowVal = 0, _oldVal = 0;
        var _do = function (waitTime, funTsk) {//模拟do{}while(true);
            var _endInner, _whileInner;
            _whileInner = function (funcondition) {
                _endInner = function (funEnd) {
                    var _funWhile = function () {
                        if (funcondition()) {
                            _endInner(funEnd);
                        } else {
                            funEnd();
                        }
                    };
                    var _runInner = function () {

                        var _previous = new Date();
                        result = funTsk.apply(context, args);
                        var _remaining = waitTime - ((new Date()) - _previous);
                        setTimeout(_funWhile, _remaining);//延迟一段时间做判断
                    };
                    _runInner();
                };
                return {
                    "end": _endInner
                };
            };
            return { "while": _whileInner };
        };


        return function () {
            context = this;
            args = arguments;
            if (_taskRun) {
                _nowVal++;
            }
            else {
                _taskRun = true;

                _do(
                    wait,//间隔时间
                     function () {
                         _oldVal = _nowVal//记录基准值
                         func();
                     }
                )
                .while(
                    function () {
                        return _oldVal != _nowVal
                    }
                )
                .end(
                    function () {
                        _taskRun = false;//执行结束重置状态变量
                        _nowVal = 0;
                    }
                );
            }
            return result;
        }
    }

  

本文版权归作者和博客园共有,未经作者本人同意禁止任何形式的转载,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。

最后再贴出测试代码:

<html>
<head>
    <title>
        “如何稀释scroll事件”测试
    </title>
    <meta charset="utf-8">
</head>
<body>
    <div style="margin:auto;600px;padding:20px">
        <input id="waitTime" type="text" value="100" onchange="onscrollTest()" style="float:left" />
        <select id="sel" onchange="onscrollTest()" style="float:left">
            <option value="1">使用_lazyRun</option>
            <option value="2">使用debounce</option>
            <option value="3">使用throttle </option>
            <option value="4">使用_asynFun </option>
        </select>
        <div id="outDiv" style="float:left"></div>

    </div>
    <div style="auto;text-align:right;font-weight:bold;color:red;padding:10px;font-size:20px">滚动条----------------------------------------------></div>
    <div id="box" style="auto; height:200px;overflow-y:scroll;">
        <div style="border-color:goldenrod;border: 2px solid #f60;height:6000px;auto;background-color:goldenrod"></div>
    </div>
</body>
</html>


<script type="text/javascript">


    var _lazyRun = function (func, wait) {
        var _preIndex = 0, _nowIndex = 1, _timer, _fnCur, _context, _result;
        var _fn1 = function () {
            if (_preIndex < _nowIndex) {
                var _previous = new Date();
                _fnCur = _fn2;
                clearTimeout(_timer);
                _preIndex = _nowIndex;
                _result = func.apply(_context, _args);
                var _remaining = wait - ((new Date()) - _previous);
                if (_remaining < 0) {
                    _result = _fn1.apply(_context, _args);
                } else {
                    _timer = setTimeout(_fn1, _remaining);//脱离线程
                }
            } else {
                _fnCur = _fn1;
                _preIndex = 0, _nowIndex = 1;
            }
            return _result;

        };
        var _fn2 = function () {
            _nowIndex++;
            return _result;
        };
        _fnCur = _fn1;
        return function () {
            _context = this;
            _args = arguments;
            _result = _fnCur.apply(_context, _args);
            return _result;
        };
    };


    //**************************underscore.js 的 debounce
    /**
    * [debounce description]
    * @param  {[type]} func      [回调函数]
    * @param  {[type]} wait      [等待时长]
    * @param  {[type]} immediate [是否立即执行]
    * @return {[type]}           [description]
    */
    var _ = {};
    _.debounce = function (func, wait, immediate) {
        var timeout, args, context, timestamp, result;

        var later = function () {
            var last = _.now() - timestamp;

            //小于wait时间,继续延迟wait-last执行later,知道last >= wait才执行func
            if (last < wait && last > 0) {
                timeout = setTimeout(later, wait - last);
            } else {
                timeout = null;
                if (!immediate) {
                    result = func.apply(context, args);

                    if (!timeout) context = args = null;
                }
            }
        };

        return function () {
            context = this;
            args = arguments;
            timestamp = _.now();
            //是否立即执行
            var callNow = immediate && !timeout;

            if (!timeout) timeout = setTimeout(later, wait);

            if (callNow) {
                result = func.apply(context, args);
                context = args = null;
            }

            return result;
        };
    };

    _.now = Date.now || function () {
        return new Date().getTime();
    };

    //**************************typeahead.js 的 throttle
    var throttle = function (func, wait) {
        var context, args, timeout, result, previous, later;
        previous = 0;
        later = function () {
            previous = new Date();
            timeout = null;
            result = func.apply(context, args);
        };
        return function () {
            var now = new Date(),
                remaining = wait - (now - previous);
            context = this;
            args = arguments;
            if (remaining <= 0) {   //如果大于间隔时间(wait)
                clearTimeout(timeout);
                timeout = null;
                previous = now;
                result = func.apply(context, args);
            } else if (!timeout) {  //小于,延时调用later
                timeout = setTimeout(later, remaining);
            }
            return result;
        };
    };



    ///导步do{}while
    var _asynFun = function (func, wait) {
        var context, args, result, _taskRun = false, _nowVal = 0, _oldVal = 0;
        var _do = function (waitTime, funTsk) {//模拟do{}while(condition);
            var _endInner, _whileInner;
            _whileInner = function (funcondition) {
                _endInner = function (funEnd) {
                    var _funWhile = function () {
                        if (funcondition()) {
                            _endInner(funEnd);
                        } else {
                            funEnd();
                        }
                    };
                    var _runInner = function () {
                        var _previous = new Date();
                        result = funTsk.apply(context, args);
                        var _remaining = waitTime - ((new Date()) - _previous);
                        setTimeout(_funWhile, _remaining);//延迟一段时间做判断
                    };
                    _runInner();
                };
                return {
                    "end": _endInner
                };
            };
            return { "while": _whileInner };
        };


        return function () {
            context = this;
            args = arguments;
            if (_taskRun) {
                _nowVal++;
            }
            else {
                _taskRun = true;

                _do(
                    wait,//间隔时间
                     function () {
                         _oldVal = _nowVal//记录基准值
                         func();
                     }
                )
                .while(
                    function () {
                        return _oldVal != _nowVal
                    }
                )
                .end(
                    function () {
                        _taskRun = false;//执行结束重置状态变量
                        _nowVal = 0;
                    }
                );
            }
            return result;
        }
    }


    //**************************测试***********************************
    var _testCount = 0;
    var _test = function () {
        console.log(_.now())
        _testCount++;
        //console.log(window.scrollY || document.documentElement.scrollTop);

    };
    function onscrollTest() {
        _testCount = 0;
        var _waitTime = document.getElementById("waitTime").value;
        $box = document.getElementById("box");
        switch (document.getElementById("sel").value) {
            case "1"://_lazyRun
                document.getElementById("outDiv").innerText = "use _lazyRun function ,wait time is " + _waitTime;
                $box.onscroll = _lazyRun(_test, _waitTime);
                break;
            case "2"://debounce
                document.getElementById("outDiv").innerText = "use debounce function ,wait time is " + _waitTime;
                $box.onscroll = _.debounce(_test, _waitTime);
                break;
            case "3"://throttle

                document.getElementById("outDiv").innerText = "use throttle function ,wait time is " + _waitTime;
                $box.onscroll = throttle(_test, _waitTime);
                break;
            case "4"://throttle

                document.getElementById("outDiv").innerText = "use _asynFun function ,wait time is " + _waitTime;
                $box.onscroll = _asynFun(_test, _waitTime);
                break;
        };
        console.clear();

    }
    onscrollTest();
</script>
测试页面HTML
原文地址:https://www.cnblogs.com/Grart/p/4148383.html