函数防抖和节流

news2024/11/16 10:33:53

函数防抖和节流

经典真题

  • 防抖,节流是什么,如何实现 (字节)

什么是函数防抖和节流

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

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

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

  • mousemove 事件:如果要实现一个拖拽功能,需要一路监听 mousemove 事件,在回调中获取元素当前位置,然后重置 DOM 的位置来进行样式改变。如果不加以控制,每移动一定像素而触发的回调数量非常惊人,回调中又伴随着 DOM 操作,继而引发浏览器的重排与重绘,性能差的浏览器可能就会直接假死。

  • window.onresize 事件:为 window 对象绑定了 resize 事件,当浏览器窗口大小被拖动而改变的时候,这个事件触发的频率非常之高。如果在 window.onresize 事件函数里有一些跟 DOM 节点相关的操作,而跟 DOM 节点相关的操作往往是非常消耗性能的,这时候浏览器可能就会吃不消而造成卡顿现象。

  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)

  • 搜索联想(keyup 事件)

  • 监听滚动事件判断是否到页面底部自动加载更多(scroll 事件)

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

函数防抖

我们首先来看函数防抖。函数防抖,是指防止函数在极短的时间内反复调用,造成资源的浪费

考虑一下电梯关门的场景,现代的大部分电梯都可以通过红外,感知到是否有人进入,为了避免夹到人,同时为了等待后面的人,电梯关门的时间往往有这么一种规则:**始终保证电梯门在最后一个人进入后 3 秒后关闭。**如果有人进入后,还没有等到 3 秒又有人进来了,电梯门会以最后一次进入的时间为计时起点,重新等待3秒。

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

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

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

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

再考虑一个搜索的场景(例如百度),当我在一个文本框中输入文字(键盘按下事件)时,需要将文字发送到服务器,并从服务器得到搜索结果,这样的话,用户直接输入搜索文字就可以了,不用再去点搜索按钮,可以提升用户体验,类似于下面的效果:

5be978627ac99

上面的效果,我没有点击搜索按钮,也没有按回车键,只是写了一些搜索的文字而已。

可是如何来实现上面的场景呢?

如果文本框的文字每次被改变(键盘按下事件),我都要把数据发送到服务器,得到搜索结果,这是非常恐怖的!

想想看,我搜索 “google” 这样的单词,至少需要按 6 次按键,就这一个词,我需要向服务器请求 6 次,并让服务器去搜索 6 次,但我只需要最后一次的结果就可以了。如果考虑用户按错的情况,发送请求的次数更加恐怖。这样就造成了大量的带宽被占用,浪费了很多资源。

如何避免这样的问题呢?

仔细观察,你会发现,真正的搜索行为,并不是每次按键都会触发的,只有当用户停止按键一段事件后才会触发。

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

  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);
    }
}

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

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

效果如下:

2021-10-01 10.06.44

函数节流

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

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

这里做一个形象的的比喻:

前面我们所介绍的函数防抖,是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。

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

关于节流的实现,有 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;
        }
    }
}

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

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

效果如下:

2021-10-01 10.27.15

(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);
        }
    }
}

真题解答

  • 防抖,节流是什么,如何实现 (字节)

参考答案:

我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove、resize、onscroll 等,有些时候,我们并不能或者不想频繁触发事件,这时候就应该用到函数防抖和函数节流。

函数防抖(debounce),指的是短时间内多次触发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行。

具体实现如下:

/**
 * 函数防抖
 * @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);
    }
}

函数节流(throttle),指连续触发事件但是在 n 秒中只执行一次函数。即 2n 秒内执行 2 次… 。

节流如字面意思,会稀释函数的执行频率。

下面是使用时间戳方式的具体实现:

/**
 * 
 * @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;
        }
    }
}

-EOF-

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1019051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DMA简单总结

文章目录 一、基础概念1.1 DMA---Direct Memory Access 直接存储器访问&#xff0c;目的减少CPU资源占用 二、典型DMA硬件模型2.1 基本硬件特性---通道数、源/目标类型&#xff0c;地址与累加方式&#xff0c;数据位宽&#xff0c;搬移长度&#xff0c;循环模式&#xff0c;中断…

在c#中使用CancellationToken取消任务

目录 &#x1f680;介绍&#xff1a; &#x1f424;简单举例 &#x1f680;IsCancellationRequested &#x1f680;ThrowIfCancellationRequested &#x1f424;在控制器中使用 &#x1f680;通过异步方法的参数使用cancellationToken &#x1f680;api结合ThrowIfCancel…

‘XXX’ is already defined @typescript-eslint/no-redeclare 警告 问题解决

上文ReactTypescript项目环境中搭建并使用redux环境 结束是 其实不算完全写完吧 还会留下一个警告 这个报错 好像是说 这两个值已经定义过了 可能很多人 会觉得小问题 但你会发现 无论你名字怎么改都会一直 带着你新的名字继续报错 我们可以多套一层 export namespace Const…

Java8实战-总结29

Java8实战-总结29 并行数据处理与性能并行流将顺序流转换为并行流测量流性能 并行数据处理与性能 到目前为止&#xff0c;Stream接口最重要的好处是可以对这些集合执行操作流水线&#xff0c;能够自动利用计算机上的多个内核。 例如&#xff0c;在Java 7之前&#xff0c;并行…

Python 异常的概念

视频版教程 Python3零基础7天入门实战视频教程 异常是指程序运行的过程中出现了错误&#xff0c;也叫Bug。 案例一&#xff1a; # 定义人类 class Person:# 属性 姓名name None# 属性 年龄age Noneprint(Person().sex)因为Person类没有sex属性&#xff0c;对象访问sex属性…

Go语言开发环境搭建指南:快速上手构建高效的Go开发环境

Go 官网&#xff1a;https://go.dev/dl/ Go 语言中文网&#xff1a;https://studygolang.com/dl 下载 Go 的语言包 进入官方网站 Go 官网 或 Go 语言中文网&#xff1a; 选择下载对应操作系统的安装包&#xff1a; 等待下载完成&#xff1a; 安装 Go 的语言包 双击运行上…

HuggingFace Transformer

NLP简介 HuggingFace简介 hugging face在NLP领域最出名&#xff0c;其提供的模型大多都是基于Transformer的。为了易用性&#xff0c;Hugging Face还为用户提供了以下几个项目&#xff1a; Transformers(github, 官方文档): Transformers提供了上千个预训练好的模型可以用于不…

GitHub 曝出漏洞,或导致 4000 多个存储库遭受劫持攻击

The Hacker News 网站披露&#xff0c;安全研究员发现 GitHub 中存在一个新安全漏洞&#xff0c;该漏洞可能导致数千个存储库面临劫持攻击的风险。据悉&#xff0c;在 2023 年 3 月 1 日漏洞披露后&#xff0c;微软旗下的代码托管平台已于 2023 年 9 月 1 日解决了安全漏洞问题…

无涯教程-JavaScript - FACT函数

描述 The FACT function returns the factorial of a number. The factorial of a number is equal to 1&ast;2&ast;3&ast;...&ast; number. 语法 FACT (number)争论 Argument描述Required/OptionalNumberThe nonnegative number for which you want the f…

LeetCode 39. Combination Sum【回溯,剪枝】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

C#小知识

项目编译后复制文件到生成目录 方法1 对于单个文件&#xff0c;可以点击属性。输出目录里选择始终复制。 方法2 把项目中的ServerScripts复制到输出目录。 在项目设置中&#xff0c;生成事件里添加批处理 xcopy $(ProjectDir)ServerScripts\*.* $(TargetDir)ServerScrip…

cms之帝国cms安装

内容摘要 帝国网站管理系统&#xff0c;英文名称为EmpireCMS&#xff0c;简称“帝国CMS”&#xff0c;本文将介绍帝国网站管理系统的安装方法。 前言&#xff1a; 本文安装教程是以帝国CMS7.5版本为基础进行图文讲解。 各位看官&#xff0c;一定要按照每个步骤去执行&#xf…

人机中的事实与价值时空、排序

人机结合智能与事实价值融合分析确实是未来解决复杂疑难问题的基本策略之一。该策略利用人类智慧和机器智能相结合&#xff0c;结合有效的事实和价值分析方法&#xff0c;以更全面、准确、高效地解决问题。 通过人机结合&#xff0c;可以充分发挥人类的主观能动性、判断力和创造…

【Java 基础篇】Java 字节流详解:从入门到精通

Java中的字节流是处理二进制数据的关键工具之一。无论是文件操作、网络通信还是数据处理&#xff0c;字节流都发挥着重要作用。本文将从基础概念开始&#xff0c;深入探讨Java字节流的使用&#xff0c;旨在帮助初学者理解和掌握这一重要主题。 什么是字节流&#xff1f; 在Ja…

【JavaSE笔记】类和对象(万字详解)

一、前言 Java是一种广泛应用于各个领域的编程语言&#xff0c;它的面向对象编程范式使得它成为了当今软件开发的主要选择之一。通过面向对象编程&#xff0c;Java使程序员能够将代码组织成易于理解和维护的结构&#xff0c;并且在开发大型复杂的应用程序时提供了许多便利。 …

STM32单片机——DMA数据传输

STM32单片机——DMA数据传输 DMA相关概述实验一&#xff1a;内存到内存搬运Cubemx工程配置Hal库程序设计及实现固件库程序设计及实现 实验二&#xff1a;内存到外设&#xff08;DMA串口发送&#xff09;Cubemx工程配置Hal库程序设计及实现固件库程序设计 实验三&#xff1a;外设…

FPGA-结合协议时序实现UART收发器(六):仿真模块SIM_uart_drive_TB

FPGA-结合协议时序实现UART收发器&#xff08;六&#xff09;&#xff1a;仿真模块SIM_uart_drive_TB 仿真模块SIM_uart_drive_TB&#xff0c;仿真实现。 vivado联合modelsim进行仿真。 文章目录 FPGA-结合协议时序实现UART收发器&#xff08;六&#xff09;&#xff1a;仿真模…

2023-09-17 LeetCode每日一题(打家劫舍 II)

2023-09-17每日一题 一、题目编号 213. 打家劫舍 II二、题目链接 点击跳转到题目位置 三、题目描述 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋&#xff0c;每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 &#xff0c;这意味着第一个房屋和最后一个房…

C#,《小白学程序》第二十六课:大数乘法(BigInteger Multiply)的Toom-Cook 3算法及源程序

凑数的&#xff0c;仅供参考。 1 文本格式 /// <summary> /// 《小白学程序》第二十六课&#xff1a;大数&#xff08;BigInteger&#xff09;的Toom-Cook 3乘法 /// Toom-Cook 3-Way Multiplication /// </summary> /// <param name"a"></par…

如何隐藏windows10系统任务栏右下角的语言输入法图标?

勾选“使用桌面语言栏(如果可用)”&#xff0c;任务栏的输入法图标立刻消失