• JS 防抖和节流函数


    防抖

    防抖技术即是可以把多个顺序地调用合并成一次,是在停止操作的一定时间内,规定事件才被触发一次。

    通俗一点来说,看看下面这个简化的例子:

    // 防抖动函数
    function debounce (func, wait, immediate) {
        var timeout;
        return function () {
            var context = this, args = arguments;
            var later = function () {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    };
     
    var myEfficientFn = debounce(function() {
        // 滚动中的真正的操作
    }, 250);
     
    // 绑定监听
    window.addEventListener('resize', myEfficientFn);
    

    大概功能就是如果 250ms 内没有连续触发两次 scroll 事件,那么才会触发我们真正想在 scroll 事件中触发的函数。

    节流(Throttling)

    防抖函数确实不错,但是也存在问题,譬如图片的懒加载,我希望在下滑过程中图片不断的被加载出来,而不是只有当我停止下滑时候,图片才被加载出来。又或者下滑时候的数据的 ajax 请求加载也是同理。

    这个时候,我们希望即使页面在不断被滚动,但是滚动 handler 也可以以一定的频率被触发(譬如 250ms 触发一次),这类场景,就要用到另一种技巧,称为节流函数(throttling)。

    节流函数,只允许一个函数在 X 毫秒内执行一次。

    与防抖相比,节流函数最主要的不同在于它保证在 X 毫秒内至少执行一次我们希望触发的事件 handler。

    与防抖相比,节流函数多了一个 mustRun 属性,代表 mustRun 毫秒内,必然会触发一次 handler ,同样是利用定时器,看看简单的示例

    // 简单的节流函数
    function throttle (func, wait, mustRun) {
        var timeout,
            startTime = new Date();
     
        return function () {
            var context = this,
                args = arguments,
                curTime = new Date();
     
            clearTimeout(timeout);
            // 如果达到了规定的触发时间间隔,触发 handler
            if(curTime - startTime >= mustRun){
                func.apply(context, args);
                startTime = curTime;
            // 没达到触发间隔,重新设定定时器
            } else {
                timeout = setTimeout(func, wait);
            }
        };
    };
    // 实际想绑定在 scroll 事件上的 handler
    function realFunc(){
        console.log("Success");
    }
    // 采用了节流函数
    window.addEventListener('scroll', throttle(realFunc,500,1000));
    

    上面简单的节流函数的例子可以拿到浏览器下试一下,大概功能就是如果在一段时间内 scroll 触发的间隔一直短于 500ms ,那么能保证事件我们希望调用的 handler 至少在 1000ms 内会触发一次。

    使用 rAF(requestAnimationFrame)触发滚动事件

    window.requestAnimationFrame() 这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数。这个方法接受一个函数为参,该函数会在重绘前调用。

    rAF 常用于 web 动画的制作,用于准确控制页面的帧刷新渲染,让动画效果更加流畅,当然它的作用不仅仅局限于动画制作,我们可以利用它的特性将它视为一个定时器。(当然它不是定时器)

    通常来说,rAF 被调用的频率是每秒 60 次,也就是 1000/60 ,触发频率大概是 16.7ms 。(当执行复杂操作时,当它发现无法维持 60fps 的频率时,它会把频率降低到 30fps 来保持帧数的稳定。)

    var ticking = false; // rAF 触发锁
     
    function onScroll(){
        if(!ticking) {
            requestAnimationFrame(realFunc);
            ticking = true;
        }
    }
     
    function realFunc(){
        // do something...
        console.log("Success");
        ticking = false;
    }
    // 滚动事件监听
    window.addEventListener('scroll', onScroll, false);
    

    上面简单的使用 rAF 的例子可以拿到浏览器下试一下,大概功能就是在滚动的过程中,保持以 16.7ms 的频率触发事件 handler。

    使用 requestAnimationFrame 优缺点并存,首先我们不得不考虑它的兼容问题,其次因为它只能实现以 16.7ms 的频率来触发,代表它的可调节性十分差。但是相比 throttle(func, xx, 16.7) ,用于更复杂的场景时,rAF 可能效果更佳,性能更好。

    总结

    debounce 防抖动: 防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。

    throttling 节流函数: 只允许一个函数在 X 毫秒内执行一次,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。

    requestAnimationFrame: 16.7ms 触发一次 handler,降低了可控性,但是提升了性能和精确度。

    参照

    http://www.cnblogs.com/coco1s/p/5499469.html

    附录-其他成熟的函数库

    underscore提供了比较成熟的防抖,节流函数,参见下文

    防抖函数

    /**
     * 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
     *
     * @param  {function} func        传入函数
     * @param  {number}   wait        表示时间窗口的间隔
     * @param  {boolean}  immediate   设置为ture时,调用触发于开始边界而不是结束边界
     * @return {function}             返回客户调用函数
     */
    _.debounce = function (func, wait, immediate) {
        var timeout, args, context, timestamp, result;
    
        var later = function () {
            // 据上一次触发时间间隔
            var last = _.now() - timestamp;
    
            // 上次被包装函数被调用时间间隔last小于设定时间间隔wait
            if (last < wait && last > 0) {
                timeout = setTimeout(later, wait - last);
            } else {
                timeout = null;
                // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
                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;
        };
    };
    
    

    节流函数

    /**
     * 频率控制 返回函数连续调用时,func 执行频率限定为 次 / wait
     * 
     * @param  {function}   func      传入函数
     * @param  {number}     wait      表示时间窗口的间隔
     * @param  {object}     options   如果想忽略开始边界上的调用,传入{leading: false}。
     *                                如果想忽略结尾边界上的调用,传入{trailing: false}
     * @return {function}             返回客户调用函数   
     */
    _.throttle = function (func, wait, options) {
        var context, args, result;
        var timeout = null;
        // 上次执行时间点
        var previous = 0;
        if (!options) options = {};
        // 延迟执行函数
        var later = function () {
            // 若设定了开始边界不执行选项,上次执行时间始终为0
            previous = options.leading === false ? 0 : _.now();
            timeout = null;
            result = func.apply(context, args);
            if (!timeout) context = args = null;
        };
        return function () {
            var now = _.now();
            // 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。
            if (!previous && options.leading === false) previous = now;
            // 延迟执行时间间隔
            var remaining = wait - (now - previous);
            context = this;
            args = arguments;
            // 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口
            // remaining大于时间窗口wait,表示客户端系统时间被调整过
            if (remaining <= 0 || remaining > wait) {
                clearTimeout(timeout);
                timeout = null;
                previous = now;
                result = func.apply(context, args);
                if (!timeout) context = args = null;
                //如果延迟执行不存在,且没有设定结尾边界不执行选项
            } else if (!timeout && options.trailing !== false) {
                timeout = setTimeout(later, remaining);
            }
            return result;
        };
    };
    
    
  • 相关阅读:
    JavaScript 基本类型值-Undefined、Null、Boolean
    Git学习之路(6)- 分支操作
    Git学习之路(5)- 同步到远程仓库及多人协作问题
    setTimeout小总结
    Git学习之路(4)- 撤销操作、删除文件和恢复文件
    Git学习之路(3)-提交文件到三个区
    Git学习之路(2)-安装GIt和创建版本库
    Git学习之路(1)-Git简介
    两种常见挂载Jenkins slave节点的方法
    rabbitmq集群节点操作
  • 原文地址:https://www.cnblogs.com/everlose/p/12501229.html
Copyright © 2020-2023  润新知