• javaScript中的函数防抖和节流


    什么是函数防抖和节流

    JavaScript 中的函数大多数情况下都是由用户主动调用触发的,除非是函数本身的实现不合理,否则一般不会遇到跟性能相关的问题。

    但是在一些少数情况下,函数的触发不是由用户直接控制的。在这些场景下,函数有可能被非常频繁地调用,而造成大的性能问题。解决性能问题的处理办法就有函数防抖函数节流

    下面是函数被频繁调用的常见的几个场景:

    • mousemove 事件:如果要实现一个拖拽功能,需要一路监听 mousemove 事件,在回调中获取元素当前位置,然后重置 DOM 的位置来进行样式改变。如果不加以控制,每移动一定像素而触发的回调数量非常惊人,回调中又伴随着 DOM 操作,继而引发浏览器的重排与重绘,性能差的浏览器可能就会直接假死。
    • window.onresize 事件:为 window 对象绑定了 resize 事件,当浏览器窗口大小被拖动而改变的时候,这个事件触发的频率非常之高。如果在 window.onresize 事件函数里有一些跟 DOM 节点相关的操作,而跟 DOM 节点相关的操作往往是非常消耗性能的,这时候浏览器可能就会吃不消而造成卡顿现象。
    • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
    • 搜索联想(keyup 事件)
    • 监听滚动事件判断是否到页面底部自动加载更多(scroll 事件)

    对于这些情况的解决方案就是函数防抖(debounce)或函数节流throttle),其核心就是限制某一个方法的频繁触发

    函数防抖

    函数防抖,是指防止函数在极短的时间内反复调用,造成资源的浪费

    考虑一个页面上的场景,页面上的某些事件触发频率非常高,比如滚动条滚动、窗口尺寸变化、鼠标移动等,如果我们需要注册这类事件,不得不考虑效率问题,又特别是事件处理中涉及到了大量的操作,比如:

    window.onresize = function(){
        // 大量的 DOM 操作
    }
    
    • 1
    • 2
    • 3

    当窗口尺寸发生变化时,哪怕只变化了一点点,都有可能造成成百上千次对处理函数的调用,这对网页性能的影响是极其巨大的。

    于是,我们可以考虑,每次窗口尺寸变化、滚动条滚动、鼠标移动时,不要立即执行相关操作,而是等一段时间,以窗口尺寸停止变化、滚动条不再滚动、鼠标不再移动为计时起点,一段时间后再去执行操作,就像生活中电梯关门那样。

    于是,为了满足这种类型场景,我们可以开发一个通用的函数,这个函数要满足以下功能:

    1. 调用该函数后,不立即做事,而是一段时间后去做事
    2. 如果在等待时间内又调用了该函数,则重新计时

    这样的功能,就叫做函数防抖,其实就是防止函数短时间内被调用多次。

    要完成该函数,需要给予两个条件:

    1. 告诉我一段时间后要做什么事(这里应该是一个回调函数,即函数作为参数)
    2. 告诉我要等待多长时间(毫秒)

    下面我们就来封装这么一个函数防抖的通用函数:

    /**
     * 函数防抖
     * @param {function} func 一段时间后,要调用的函数
     * @param {number} wait 等待的时间,单位毫秒
     */
    function debounce(func, wait) {
        // 设置变量,记录 setTimeout 得到的 id
        var timerId = null;
        return function (...args) {
            if (timerId) {
                // 如果有值,说明目前正在等待中,清除它
                clearTimeout(timerId);
            }
            // 重新开始计时
            timerId = setTimeout(() => {
                func(...args);
            }, wait);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    下面来进行一个测试,测试如下:

    <input type="text" id="txt">
    
    • 1
    var txt = document.getElementById("txt");
    // 调用 debounce 函数来将事件处理函数变为一个防抖函数
    var debounceHandle = debounce(function(event){
      console.log(event.target.value);
    }, 500)
    txt.onkeyup = (event)=>{
      debounceHandle(event);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    函数节流

    函数节流的目的,也是为了防止一个函数短时间内被频繁的触发。

    和函数防抖的原理不同,函数节流的核心思想是让连续的函数执行,变为固定时间段间断地执行。

    这里我们要介绍的函数节流,指一定时间内函数只执行一次。比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。

    关于节流的实现,有 2 种主流的实现方式,一种是使用时间戳,一种是设置定时器

    (1)使用时间戳

    触发事件时,取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

    下面是封装使用时间戳的函数节流的通用函数:

    /**
     * 
     * @param {要进行节流的函数} func 
     * @param {间隔时间} wait 
     * @returns 
     */
    function throttle(func, wait) {
        var args; // 存储函数参数
        var previous = 0; // 一开始的默认时间
        return function () {
            var now = new Date(); // 获取最新的时间戳
            args = arguments; // 获取参数
            // 进行时间戳的判断,如果超出规定时间,则执行
            if (now - previous > wait) {
                func.apply(null, args);
                previous = now;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    下面来实际使用测试一下:

    <input type="text" id="txt">
    
    • 1
    var txt = document.getElementById("txt");
    // 调用 throttle 函数来将事件处理函数变为一个节流函数
    var throttleHandle = throttle(function (event) {
      console.log(event.target.value);
    }, 1000)
    txt.onkeyup = (event) => {
      throttleHandle(event);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (2)设置定时器

    第二种方式是设置定时器,触发事件时设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

    下面是封装使用定时器的函数节流的通用函数:

    /**
     * 
     * @param {要节流执行的函数} func 
     * @param {节流的时间间隔} wait 
     * @returns 
     */
    function throttle(func, wait) {
        // timeout 存储计时器返回值
        // args 存储参数
        var timeout, args;
        return function () {
            args = arguments;
            // 如果 timeout 有值,说明上一次的执行间隔时间还没过
            if (!timeout) {
                // 进入此 if 说明时间间隔已经过了
                // 先执行一次要执行的函数
                func.apply(null, args)
                // 然后重新设置时间间隔
                timeout = setTimeout(function () {
                    timeout = null;
                }, wait);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    其他参考:实现一个防抖函数完善一个防抖函数

  • 相关阅读:
    闭区间上连续函数的一些定理
    企业架构LNMP学习笔记45
    基于Echarts实现可视化数据大屏蓝色HTML土地交易大数据分析模板(1页)
    laravel Modify data is invalid 解决修改数据无效
    docker报错Error response from daemon: Container xxx is not running
    数据结构与算法(三):栈与队列
    树的基本概念介绍
    IBM、华为都在使用的集成产品开发IPD是什么
    数分-工具-Pandas2-pandas基础
    C语言大小端速通
  • 原文地址:https://blog.csdn.net/qq_39357822/article/details/138159229