防抖与节流:解决Web前段高频事件,性能优化的”YYDS“

一、前言

在开发中,我们经常会遇到需要频繁触发某个函数的情况,比如:

  • 监听滚动条的变化,当滚动条的位置发生变化时,需要执行某个函数;
  • 监听鼠标的移动,当鼠标的位置发生变化时,需要执行某个函数;
  • 监听键盘的按键,当键盘的某个按键被按下时,需要执行某个函数;
  • 商城抢购疯狂的点击;

当你频繁的触发用户界面时,会不停的触发事件处理函数,换而言之,当出现连续点击,上拉加载、实时搜索等对 DOM 元素频繁操作,请求资源加载等耗性能的操作,可能导致界面卡顿,浏览器奔溃,页面空白等情况。

而解决这一问题的,正是函数节流与函数防抖。

二、函数节流

1. 定义

节约(减少)触发事件处理函数的频率,连续每隔一定的时间触发执行的函数,它是优化高频率执行一段js代码的一种手段 。

这种高频操作若不进行一定的处理,必然会造成多次数据的请求,服务器的压力,这样代码的性能是非常低效的,影响性能。

降低这种频繁操作的一个重要的手段就是降低频率,通过节流控制,也就是让核心功能代码在一定的时间,隔多长时间内执行一次。

节流就是保证一段时间内只执行一次核心代码。

2. 特点

不管事件触发有多频繁,都会保证在规定的间隔时间内真正的执行一次事件处理函数,只会让一个函数在某个时间窗口内执行一次,若在时间窗口内再次触发,则重新计算时间。

3. 应用场景

  • 鼠标连续多次点击click事件;
  • 鼠标移动 mousemove、拖拽、窗口尺寸改动(resize);
  • 鼠标滚轮页面上拉(onScroll);
  • 上拉刷新懒加载等;

4. 原理:

通过判断是否达到一定的时间来触发函数,若没有规定时间则使用计时器进行延迟,而下一次事件则会重新设定计时器,它是间隔时间执行。

5. 实现方案

时间戳 + 定时器

复制代码
/* throttle1函数,节流实现方式1:时间戳+定时器
 *  @params method,duration 第一个参数为事件触发时的真正要执行的函数
 *  第二个参数duration表示为定义的间隔时间
 *
 *  原理:通过判断是否达到一定的时间来触发函数,若没有规定时间则使用计时器进行延迟,而下一次事件则会重新设定计时器,它是间隔时间执行,不管事件触发有多频繁,都会保证在规定内的事件一定会执行一次真正事件处理函数
 *
 */
function throttle1(method, duration) {
    var timer = null;
    var prevTime = new Date();   // 之前的时间
return function() {
    var that = this;
        currentTime = new Date();         // 获取系统当前时间
        resTime = currentTime - prevTime;  // 时间戳
    // 打印本次当前的世间和上次世间间隔的时间差
    console.log("时间差", resTime);
    // 当前距离上次执行时间小于设置的时间间隔
    if(resTime < duration) {
        // 清除上次的定时器,取消上次调用的队列任务,重新设置定时器。这样就可以保证500毫秒秒内函数只会被触发一次,达到了函数节流的目的
        clearTimeout(timer);
        timer = setTimeout(function(){
            prevTime = currentTime;
            method.apply(that);
        }, duration)
    }else { // 当前距离上次执行的时间大于等于设置的时间时,直接执行函数
        // 记录执行方法的时间
        prevTime = currentTime;
        method.apply(that);
    }
    
}
}

// 事件触发的方法(函数),函数节流1
function handleJieLiu1(){
console.log("节流方式1");
}   

var handleJieLiu1 = throttle1(handleJieLiu1, 500);
document.addEventListener('mousewheel', handleJieLiu1);

定时器 + 重置一个开关变量

复制代码
/*
 * throttle2函数节流实现方式2:重置一个开关变量+定时器
 * @params method,duration形参数与上面的含义一致
 * @return 返回的是一个事件处理函数
 *
 * 在throttle2执行时定义了runFlag的初始值,通过闭包返回一个匿名函数作为事件处理函数,
 *
 * 在返回的函数内部判断runFlag的状态并确定执行真正的函数method还是跳出,
   每次执行method后会更改runFlag的状态,通过定时器在durtion该规定的间隔时间内重置runFlag锁的状态
 * 
 */
function throttle2(method, duration){
  // 当前时间间隔内是否有方法执行,设置一个开关标识
  var runFlag = false;
// 返回一个事件处理函数
return function(e) {
    // 判断当前是否有方法执行,有则什么都不做,若为true,则跳出
    if(runFlag){
        return false;
    }
    // 开始执行
    runFlag = true;
    // 添加定时器,在到达时间间隔时重置锁的状态
    setTimeout(function(){
        method(e);
        // 执行完毕后,声明当前没有正在执行的方法,方便下一个时间调用
        runFlag = false;
    }, duration)
}
} 
// 事件触发的方法(函数),函数节流2
function handleJieLiu2(){
console.log("节流方式2");
}
var handleJieLiu2 = throttle2(handleJieLiu2, 500);
document.addEventListener('mousewheel', handleJieLiu2);

上面两种实现函数节流的方式都可以达到防止用户频繁操作而引起重复请求资源的。

当鼠标滚轮不断滚动时,事件处理函数的执行顺序不一样。

当给一个大范围的时间内,比如:1小时内,每几分钟执行一次,超过一小时不在执行,推荐使用第一种函数节流的方式。

如果仅仅要求间隔一定时间执行一次,推荐使用第二种函数节流的方式。

三、函数防抖

1. 定义

防止抖动,重复的触发、频繁操作,核心在于,延迟事件处理函数的执行,一定时间间隔内只执行最后一次操作,就是当函数被触发后,只有在上一次函数执行完,一段时间后,才会再次触发函数。

例如:表单多次提交,推荐使用防抖。

换句话说,也就是当连续触发事件时并没有执行事件处理函数,只有在某一阶段连续触发的最后一次才执行,它遵循两个条件:

  • 必须要等待一段时间
  • 上一次触发的时间间隔要大于设定值才执行

2. 特点

某段时间内只执行一次。

在生活中,你可以想象公交司机等人上车后,才出站一样

3. 应用场景

常应用于输入框事件keydown、keyup、搜索联想查询等,只有在用户停止键盘输入时才发送Ajax请求。

4. 原理

它是维护一个计时器,规定在duration(延迟)时间后出过事事件处理函数,但是在duration时间内再次触发的话,都会清除当前的timer重新计时,这样一来,只有最后一次操作事件处理函数才被真正的触发。

5. 实现方案

复制代码
/*
 *  函数防抖
 *  例如:假定时间间隔时500ms,频繁不同的操作5s,且每两次执行时间小于等于间隔500ms
 *  那么最后只执行了1次,也就是每一次执行时都结束上一次的执行
 *  @params method,duration,与上面一致
 *
 *  原理:它是维护一个计时器,规定在duration时间后出发时间处理函数,但是在duration时间内再次出发的化,都会清除当前的timer重新计时,这样一来,只有最后一次操作事件处理函数才被真正的触发
 *
 * 一般用于输入框事件,常用场景就是表单的搜索或者联想查询,如果不使用防抖会连续发送请求,增加服务器的压力,使用防抖后,会在用户输入要查询的关键词后才发送请求,百度搜索就是这么实现的
 *
 * 
 */
function debounce(method, duration) {
var timer = null;
return function(){
    var that = this,
        args = arguments;
    // 在本次调用之间的一个间隔时间内若有方法在执行,则终止该方法的执行
    if(timer) {
        clearTimeout(timer);
    }
    // 开始执行本次调用
    timer = setTimeout(function(){
        method.apply(that,args);
    }, duration)

}

}
    // 事件触发的方法(函数),防抖
function handleFangDou(){
    console.log("函数的防抖",new Date());
}
var handleFangDou = debounce(handleFangDou, 500);
var oInput = document.querySelector("#input"); // 获取input元素
oInput.addEventListener('keyup',handleFangDou);

如上输入框效果所示,每当输入框输入后,键盘弹起时,执行事件处理函数,而不应该是键入内容时都触发一次事件处理函数。

同理,搜索引擎、表单联想查询功能时,不是根据用户键入的字母、数字、内容等同时进行 Ajax 数据请求的。如果每键入一个字母都触发一次数据请求,那是会非常耗性能。应当是用户停止输入的时候才去触发查询请求,这个时候就用到函数防抖了的。

表单的多次提交,百度搜索等都是用的防抖实现的。

四、对比

1. 共同点

都是解决频繁操作触发事件处理函数,引起页面卡顿,不流畅等性能问题,都是通过设置延时计时器逻辑来提升性能,以减少http请求次数,节约请求资源。

2. 不同点

函数节流,间隔时间内执行事件处理函数。

函数防抖,一定时间间隔内只执行最后一次操作。

五、第三方库实现

如果不想自己手动实现,可以直接引入 lodash 库。

安装 yarn add lodashnpm i lodash 然后引入。

1. 节流实现

复制代码
import throttle from 'lodash/throttle'; // 引入lodash.throttle库

// 事件触发的方法(函数),节流
function handleThrottle(){
    console.log("函数的节流",new Date());
}

throttle(handleThrottle, 500); // 将触发事件处理函数作为第一个参数传入,第二个参数为间隔的时间,这里是500毫秒

2. 防抖实现

复制代码
import debounce from 'lodash/debounce'; // 函数防抖

function handleDebounce() {
    console.log("函数的防抖", new Date());
}                        

debounce(handleDebounce, 500);

对于函数的节流与防抖是前端提升性能的手段,虽然就几行代码,但是面试时,常问不衰。让你手写时,很多时候拍拍胸脯,不借助搜索,还真不一定能写得出来。

在实际的开发中,函数的节流与函数防抖也是比较频繁的,可见它的重要性不言而喻。


🎁 福利时间

如果你正在备战面试或者想要学习其他知识,给大家推荐一个宝藏知识库,作者整理了一些列 Java 程序员需要掌握的核心知识,有需要的自取不谢。

知识库地址:https://farerboy.com/


相关推荐
KaMeidebaby1 小时前
卡梅德生物技术快报|核酸适配体文库筛选:核酸适配体文库筛选全流程技术解析:NGS与AI辅助方案的设计与实践
前端·人工智能·物联网·算法·百度
Wect1 小时前
TypeScript 完全指南
前端·javascript·typescript
hunterandroid1 小时前
Fragment 事务与状态丢失:从崩溃现场到稳定治理
前端
清风programmer1 小时前
H5与小程序通信
前端·vue.js·uni-app
不一样的少年_2 小时前
原来 AI Agent 的核心循环这么简单:手搓一个 Agent Loop
前端·后端·agent
爱勇宝2 小时前
你以为自己性格不好,其实只是被环境反复训练
前端·后端·程序员
phltxy2 小时前
LangChain_v1_Agent快速开发和更新说明
前端·javascript·langchain
Cobyte2 小时前
通过 Vite 运行手写的模板编译器
前端·javascript·vue.js
Hyyy2 小时前
Agent 工作流
前端