在JavaScript中的防抖函数 - 通过在React中构建自动完成功能来解释

当你将一个新应用推向生产环境时,你希望确保它用户友好。网站的性能是用户体验的关键部分。每个用户都希望网站及其内容能够快速加载。每一秒都是宝贵的,可能导致用户再也不会访问你的网站。

在本指南中,我们将了解JavaScript中一个非常重要的技术,即防抖函数。然后,我将向您展示如何在React中使用防抖实现自动完成功能。

(本文内容参考:java567.com)

目录:

  • 什么是防抖函数?
  • 如何在JavaScript中实现防抖?
  • 防抖函数的用例
  • 结论

什么是防抖函数?

防抖是一种通过控制函数执行的时间来改善功能性能的策略。

防抖接受一个函数并将其转换为更新后(防抖的)函数,以便在一定时间后执行原始函数中的代码。

如果在该时间段内再次调用防抖函数,则会重置先前的计时器,并为此函数调用启动一个新的计时器。这个过程对每个函数调用都会重复。

一个例子会让你更好地理解。让我们拿一个函数fun()来说。我们希望在500毫秒后执行这个函数。

function fun() {
    console.log('This is a function')
}

经过防抖处理后,会返回一个新的函数debouncedFun()。现在,每当你调用debouncedFun()时,它都会在500毫秒后执行。

如果你在第一次调用后的500毫秒内再次调用它,先前的计时器会被重置,并为第二次函数调用启动一个新的计时器。如果你在500毫秒内保持调用函数,这个过程会重复。

如何在JavaScript中实现防抖?

让我们了解如何在JavaScript中实现防抖。首先,我们将讨论我们的需求。我们希望防抖函数的行为是什么?

  • 延迟一定时间执行函数。
  • 如果再次调用函数,则重置计时器。

为了防抖一个函数,我们将有一个单独的函数,它接受函数引用和延迟作为参数,并返回一个防抖函数。

function debounce(func, delay) {
      return () => {}   // 返回防抖函数
}

这个函数只会被调用一次,以返回一个防抖函数,并且这个防抖函数将在后续的代码中使用。

要延迟一段时间执行函数,我们可以简单地在JavaScript中使用setTimeout函数。

function debounce(func, delay) {
    return () => {
        setTimeout(() => {
        	func()
        }, delay)
    }
}

这会将函数调用延迟delay毫秒。但是,这还不完整,因为它只满足了第一个要求。我们如何实现第二个行为呢?

让我们创建一个变量timeout,并将其赋值为setTimeout方法的返回值。setTimeout方法返回一个唯一的标识符给timeout,这个标识符由timeout变量持有。

function debounce(func, delay) {
    let timeout=null
    return () => {
        timeout=setTimeout(() => {
            func()
        }, delay)
    }
}

每次你调用setTimeout时,ID都是不同的。我们将使用这个timeout变量来重置计时器。

但是我们如何从debounce()方法外部访问timeout呢?如前所述,debounce()方法只被调用一次来返回一个防抖函数。这个防抖函数再执行防抖逻辑。

那么,即使在debounce()函数之外使用debounced函数,它是如何访问timeout的呢?好吧,它使用了一个叫做闭包的概念。

JavaScript中的闭包是什么?

在JavaScript中,内部函数始终可以访问外部函数的局部变量。在我们的例子中,内部函数可以访问在debounce()方法中具有函数级作用域的timeout变量。

但是当外部函数返回这个内部函数时,即使外部函数已经执行完毕,内部函数仍然持有对外部函数局部变量的引用。这就是闭包的概念。

让我们用一个例子来理解闭包。

function outerFunction() {
  const x = 5;

  return () => {
    console.log(x);
  }
}

const inner = outerFunction();

inner(); // 输出5

// console.log(x)   抛出引用错误

在这里,如果我们调用inner(),代码将没有错误并且打印5。但是,如果我们试图直接访问x,JavaScript将抛出一个引用错误。

在这里插入图片描述
在JavaScript中引用错误

在这里,inner()封闭了x,只有这个函数可以使用这个变量,其他的函数无法访问它。

回到防抖函数

让我们回到我们离开的地方:

function debounce(func, delay) {
    let timeout=null
    return () => {
        timeout=setTimeout(() => {
            func()
        }, delay)
    }
}

在这里,JavaScript使用闭包来在每次使用防抖函数时都保持对timeout的访问。

让我们利用这一点。由于debouncedFun()在每次函数调用中都可以访问相同的timeout变量,我们可以添加一个条件来检查先前的计时器是否存在。我们可以使用一个空值检查来实现这一点,if(timeout !== null)或if(timeout)。

然后,我们使用clearTimeout()方法来取消上一个计时器,从而重置计时器。

在启动新的计时器之前,添加以下语句:

if(timeout) 
	clearTimeout(timeout)

计时器被重置后,为当前函数调用启动一个新的计时器,并将其ID分配给timeout。对于由于闭包而访问相同timeout的后续函数调用,这个过程会重复。

function debounce(func, delay) {
    let timeout=null
    return () => {
        if(timeout) clearTimeout(timeout)

        timeout=setTimeout(() => {
            func()
        }, delay)
    }
}

通过这样做,我们满足了我们的第二个要求 - 即重置计时器并启动新的计时器。现在是时候使用这个防抖函数了。

让我们将fun()传递给debounce()方法,延迟500ms。

const debouncedFun = debounce(fun, 500)

debouncedFun()基本上是带有防抖行为的fun()。让我们以不同的时间间隔调用这个函数来测试我们的功能。

debouncedFun()

setTimeout(debouncedFun, 300)

setTimeout(debouncedFun, 900)

第一个函数调用立即执行。另外两个分别在300ms和900ms后执行。你能猜到输出吗?

代码打印出两次"This is a function"。让我们来理解为什么。在这里,第一次调用后,fun()被安排在500ms后执行。但第二次调用在300ms内,这会重置计时器并启动一个新的计时器。

500ms已经过去,fun()方法执行。然后,在900ms时,又发生了另一个函数调用。这会再次在500ms后执行fun()。

我们还应该做一点小小的改进。我们的逻辑没有考虑函数参数。让我们用带有参数的fun()来替换它。

function fun(a, b) {
    console.log(`This is a function with arguments ${a} and ${b}`)
}

为了在防抖时包含参数,返回一个接受参数的防抖函数。

function debounce(func, delay) {
    let timeout=null
    return (...args) => {
        if(timeout) clearTimeout(timeout)

        timeout=setTimeout(() => {
            func(...args)
            timeout=null
        }, delay)
    }
}

通过使用展开运算符,传递给防抖函数的任何参数都将存储在args变量中的数组中。然后,使用相同的args数组展开调用实际函数。

const debouncedFun=debounce(fun, 500)
debouncedFun(2,3)

上面的代码在500毫秒后打印出"This is a function with arguments 2 and 3"。

防抖函数的用例

让我们看看防抖在实际应用中是如何使用的。防抖最常见的用例是自动完成功能。你一定见过很多网站,在输入框中输入内容时,它会显示一个结果列表。

这里有一个来自Google搜索的例子:

在这里插入图片描述

Google搜索在输入"Top 10"后的自动完成功能

Google搜索显示最近和常见的搜索项。这些信息主要来自浏览器缓存。但是,一些网站会向后端服务器发出API调用,从数据库中获取数据。

通过在输入元素上添加一个onchange事件并在事件处理程序中实现fetch逻辑,可以很容易地实现这一点。但这里有一个小问题。

考虑以下例子:

在这里插入图片描述

每次输入值都会进行API请求

当我输入单词absolute时,每次输入字段的值更改都会进行一次API请求。在很短的时间内我们进行了8次API请求,这会给后端服务器造成很大的负载,并可能导致性能问题。

理想情况下,我们希望在用户完成输入后一段时间内显示自动完成功能的结果。在这里,用户一次性输入了absolute,所以我们不需要在每次输入更改时都显示结果,而是在用户完成输入后显示结果 - 也就是说,在输入更改和结果显示之间添加一些延迟。

所以,我们只有在用户完成输入时才进行API调用,而不是在每次输入更改时都调用。这减少了API调用的数量并提高了性能。我们可以使用防抖来实现这种行为。

让我们了解如何在React中实现自动完成功能。

自动完成功能示例

使用create-react-app(或像Vite这样的现代构建工具)创建项目。删除现有的样板代码。不需要安装额外的依赖项。运行npm start命令启动项目。您可以在GitHub上找到完整的代码。

我已经设置了一个Node服务器来为应用程序获取数据。您可以在Git存储库中找到它。运行node server命令来启动它。我不打算展示Node.js代码,因为它超出了本教程的范围。

让我们开始实现。我们将编写一个简单的自动完成功能。该应用程序应该显示一个包含用户输入字符串的城市

列表。

应用程序组件

我们首先需要一个输入元素来接受用户输入,以及一个用于搜索结果的结果容器。将一个异步函数作为事件处理程序附加到输入元素上。

function App() {
    const [data, setData] = useState(null)

    const loadData = async (event) => {
        
    }
    return (
        <div className="App">
            <input type="text" onChange={(e) => loadData(e)}/>
            {data && data.length !== 0 &&
            <div className="results-container">
                {data.map(item => (
                    <div key={item.id} className="result-item">
                        <p> {item.city} </p>
                    </div>
                ))}
            </div>}
        </div>
    );
}

数据将存储为状态,并且只有在数据非空时才会显示结果。我会跳过本教程的CSS,你可以在Git Repo中找到它。

事件处理程序

loadData()函数获取我们的数据并将响应存储为状态。

const loadData = async (event) => {
	const value=event.target.value
    if(value === '') {
        setData(null)
        return
    }
    const response=await fetch(`http://localhost:8000/data/${value}`)
    const res=await response.json()
    setData(res)
}

如果没有输入值,简单地退出函数。否则,对节点服务器端点进行请求。由于此函数在每次输入更改时都会被调用,因此我们将对该函数进行防抖。

使用自定义钩子实现防抖

我们将在一个自定义钩子中编写防抖逻辑。自定义钩子的优点是您可以在整个应用程序中重用相同的逻辑。强烈建议您这样做。

创建一个新文件夹custom-hooks,在其中创建一个名为useDebounce.js的文件。如前所述,useDebounce()方法应该接受一个函数和延迟作为参数,并返回防抖函数。

const useDebounce = (func, delay) => {
    let timeout=null

    return (...args) => {
        if(timeout) clearTimeout(timeout)

        timeout=setTimeout(() => {
            func(...args)
        }, delay)
    }
}

export default useDebounce

现在,在应用程序组件内部,调用此方法一次以获取loadDataDebounced()

const loadDataDebounced = useDebounce(loadData, 400)

我们将使用这个新方法作为输入元素的事件处理程序。

<input type="text" onChange={(e) => loadDataDebounced(e)}/>
输出

在输入元素内输入搜索字符串以测试我们的代码。

在这里插入图片描述

屏幕上的输出

在这里插入图片描述

正如你在网络选项卡中看到的,只发送了一个请求,而不是三个。这使得搜索性能得到了很大的改善。

结论

在本教程中,您了解了什么是防抖,以及如何实现它。防抖通过延迟一定时间来执行函数,并在函数再次调用时重置前一个计时器来延迟函数执行。

防抖使用了一个重要的概念 - 闭包。我稍微偏离了实现来解释闭包是什么。这对于初学者来说可能是一个令人困惑的概念,所以请花些时间来理解它。闭包允许您在函数执行完毕后继续使用局部变量。

之后,我向您展示了防抖的一个常见用例 - 自动完成功能。通过防抖可以提高功能的性能。我还向您展示了如何在React中实现自动完成功能,并使用自定义钩子来实现防抖。希望这对您未来的项目有所帮助。

(本文内容参考:java567.com)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

免费chatgpt使用

基本功能如下&#xff1a; https://go.aigcplus.cc/auth/register?inviteCode3HCULH2UD

【机器学习笔记】3 逻辑回归

分类问题 分类问题监督学习最主要的类型&#xff0c;主要特征是标签离散&#xff0c;逻辑回归是解决分类问题的常见算法&#xff0c;输入变量可以是离散的也可以是连续的 二分类 先从用蓝色圆形数据定义为类型1&#xff0c;其余数据为类型2&#xff1b;只需要分类1次&#x…

蓝桥杯第九届电子类单片机组程序设计(模拟题)

目录 蓝桥杯大赛历届真题 一、第九届比赛题 二、代码实现 main.c iic.c iic.h 前言 蓝桥杯的真题可以再官网上查到&#xff0c;链接放下边了&#xff0c;点击即可跳转到官网&#xff1a; 蓝桥杯大赛历届真题 突然发现官网上的题也不全&#xff0c;而且还有一部分是模拟…

如何合理规划 PostgreSQL 的数据库用户

PostgreSQL 作为世界上最领先的开源数据库&#xff0c;有一套强大的用户角色权限系统&#xff0c;和 MySQL 做一个对比&#xff1a; 但硬币的另一面则是对于简单场景来说增加了复杂度。在许多单应用场景&#xff0c;其实也不需要额外的 schema 层&#xff0c;也不需要额外的 ow…

变量与运算符

目录 1. 关键字&#xff08;keyword&#xff09; 2. 标识符( identifier) 3. 变量 3.1 为什么需要变量 3.2 初识变量 3.3 Java中变量的数据类型 3.4 变量的使用 3.4.1 步骤1&#xff1a;变量的声明 3.4.2 步骤2&#xff1a;变量的赋值 4. 基本数据类型介绍 4.1 整数…

第23讲 微信用户管理实现

package com.java1234.entity;import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Data;import java.io.Serializable; import java.util.Date;/*** 微信用户信息实体* author java1234_小…

CPU-GPU异构并行化APSP算法

一、Floyd-Warshall算法 介绍 Floyd-Warshall算法&#xff08;英语&#xff1a;Floyd-Warshall algorithm&#xff09;&#xff0c;中文亦称弗洛伊德算法或佛洛依德算法&#xff0c;是解决任意两点间的最短路径的一种算法&#xff0c;可以正确处理有向图或负权&#xff08;但…

Github用人工智能(AI)帮你的代码修正安全漏洞

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

2024.02.13作业

21. c 22. b 23. b 5先出栈意味着1234都在栈内&#xff0c;此时1不能比2&#xff0c;3先出栈 24. b, c, d: 10, 12, 120 25. 2, 5 26. 数组越界&#xff0c;可能出现段错误 27. 0, 41 28. 1, 320 29. *a *b; *b *a - *b; *a - *b; 30. 0x801005&#xff1b;0x8…

GPT-4带来的思想火花

GPT-4能够以其强大的生成能力和广泛的知识储备激发出众多思想火花。它能够在不同的情境下生成新颖的观点、独特的见解和富有创意的解决方案&#xff0c;这不仅有助于用户突破思维定势&#xff0c;还能促进知识与信息在不同领域的交叉融合。 对于研究者而言&#xff0c;GPT-4可能…

STM32—DHT11温湿度传感器

文章目录 一.温湿度原理1.1 时序图 二.代码 一.温湿度原理 1.1 时序图 (1).下图一是DHT11总的时序图。 (2).图二对应图一的左边黑色部分&#xff0c;图三对应图一的绿色部分&#xff0c;图四的左部分图对应图一的红色部分&#xff0c;图四的右部分对应图一的黄色部分。 (3)…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Navigation组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Navigation组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Navigation组件 鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#…

嵌入式CAN通信协议原理(下)

本篇文章结合实际CAN控制器继续介绍协议相关的内容&#xff0c;还有示例讲解。 好了&#xff0c;继续吧&#xff01; 二. STM32 CAN 控制器介绍 STM32 的芯片中具有 bxCAN 控制器 (Basic Extended CAN)&#xff0c;它支持 CAN 协议 2.0A 和 2.0B 标准。 该 CAN 控制器支持最…

【C语言】socketpair 的系统调用

一、 Linux 内核 4.19socketpair 的系统调用 SYSCALL_DEFINE4(socketpair, int, family, int, type, int, protocol,int __user *, usockvec) {return __sys_socketpair(family, type, protocol, usockvec); } 这段代码定义了一个名为 socketpair 的系统调用。系统调用是操作…

JavaScript基础第三天

JavaScript 基础第三天 今天我们学习for循环、while循环、终止循环和无限循环。 1. for 循环 1.1. 语法 // 1. 语法格式 // for(起始值; 结束条件; 累加器) { // // 要重复执行的代码 // }1.2. 示例代码 let sum 0; for (let i 0; i < 100; i) {sum i; } alert(&q…

阿里云服务器4核16G配置报价和CPU内存性能参数表

阿里云4核16G服务器优惠价格ECS云服务器经济型e实例26元1个月、149元半年、79元3个月&#xff0c;4核16G通用算力u1服务器、通用型g7、通用型g8i、AMD通用型g8a、性能增强通用型g8ae、高主频通用型hfg8i、AMD通用型g7a、内存型r7p等均提供4核16G配置。阿里云服务器网aliyunfuwu…

C#通过重写虚方法实现加、减、乘、除运算 通过多态确定人类的说话行为

目录 一、涉及到的知识点1 1.虚方法 2.重写方法 3.重写方法与重载方法的区别 4.通过KeyPressEventArgs.KeyChar限制键盘输入的内容 5.if-else if-else嵌套转换为switch case 二、 涉及到的知识点2 1.多态性 2.使用多态性的注意事项 3. 使用虚方法实现多态性 三、实…

红日靶场2学习

靶场下载来自&#xff1a; http://vulnstack.qiyuanxuetang.net/vuln/detail/3/ 靶场统一登录密码&#xff1a;1qazWSX 按大佬的说法是 环境需要模拟内网和外网两个网段&#xff0c;PC端虚拟机相当于网关服务器&#xff0c;所以需要两张网卡&#xff0c;一个用来向外网提供web…

FT2232调试记录(3)

FT2232调试记录&#xff08;1&#xff09;: FT2232调试记录&#xff08;2&#xff09;: FT2232调试记录&#xff08;3&#xff09;: FT2232 SPI读写函数: 参照SPI提供的文档&#xff1a; 工程&#xff1a; SPI 写函数&#xff1a; FT_STATUS write_byte(FT_HANDLE handle…

剑指offer——数值的整数次方

目录 1. 题目描述2. 一般思路2.1 有问题的思路2.2 全面但不高效的思路2.3 面试小提示 3. 全面又高效的思路 1. 题目描述 题目:实现函数 double Power(double base,int exponent)&#xff0c;求base 的exponent 次方。不得使用库函数&#xff0c;同时不需要考虑大数问题 2. 一般…