前端面试 - 如何理解 防抖和节流?

一、什么是防抖 debounce

防抖是一种控制函数执行频率的函数,它的主要目的是延迟函数的执行,直到事件停止触发一段时间后才执行。防抖通常应用于那些用户快速频繁操作的场景中,例如搜索框的输入,防止用户每输入一个字符就触发一次请求。

工作原理

当事件发生时,防抖会重新设置计时器,如果在预定的延迟时间内没有再次触发事件,则执行目标函数;否则,重新启动计时器。

举个例子,假设我们为输入框绑定了keyup事件,当用户每输入一个字符时,我们会调用某个函数进行搜索操作。如果不进行防抖处理,用户每输入一个字符都会触发一次搜索请求,这样的行为对于性能会产生很大的压力。防抖通过延迟执行搜索请求,确保只有在用户停止输入一段时间后才真正发起请求。

防抖的应用场景

输入框实时搜索:当用户在输入框中输入内容时,触发请求进行搜索。使用防抖可以避免每次输入都发送请求,从而减轻服务器压力。

窗口调整大小:浏览器窗口大小变化时触发resize事件,频繁触发可能导致不必要的重绘。使用防抖可以减少重绘的次数。

按钮点击:防止在用户短时间内连续点击按钮,避免多次执行同一操作。

防抖的代码

javascript 复制代码
function debounce(func, delay) {  
  let timer;  
  return function (...args) {  
    if (timer) clearTimeout(timer);  
    timer = setTimeout(() => func.apply(this, args), delay);  
  };  
}  
  
const searchInput = document.querySelector("#search");  
searchInput.addEventListener("keyup", debounce(function () {  
  console.log("Searching for:", searchInput.value);  
}, 300));

我们通过debounce函数来处理输入框的keyup事件,确保只有在用户停止输入300毫秒后才会执行搜索操作。

防抖的完善代码

javascript 复制代码
const clearTimer = function clearTimer(timer) {
    if (timer) clearTimeout(timer);
    return null;
};

const debounce = function debounce(func, wait, immediate) {
    // init params
    if (typeof func !== "function") throw new TypeError("func is not a function!");
    if (typeof wait === "boolean") {
        immediate = wait;
        wait = undefined;
    }
    wait = +wait;
    if (isNaN(wait)) wait = 300;
    if (typeof immediate !== "boolean") immediate = false;
    // handler
    let timer = null;
    return function operate(...params) {
        // now:记录是否是立即执行「第一次点击&immediate=true」
        let now = !timer && immediate;
        // 清除之前设置的定时器
        timer = clearTimer(timer);
        timer = setTimeout(() => {
            // 结束边界触发
            if (!immediate) func.call(this, ...params);
            // 清除最后一个定时器
            timer = clearTimer(timer);
        }, wait);
        // 如果是立即执行,则第一次执行operate就把要干的事情做了即可 "开始边界触发"
        if (now) func.call(this, ...params);
    };
};
    

二、什么是节流(Throttle)

节流是控制函数执行频率的另一种方式,它的目的是限定函数的执行频率,保证在一定时间内函数最多只执行一次。节流技术适用于那些频繁触发,但又不要求每次都执行的场景。

工作原理

节流会以固定的时间间隔执行目标函数,每次触发事件时,只有在上次函数执行的时间超过设定的间隔,才会执行目标函数。如果触发事件的频率高于设定的时间间隔,函数会被跳过。

举个例子,假设我们要监听浏览器的滚动事件,滚动事件会非常频繁地触发。如果每次触发都执行回调函数,页面性能可能会受到很大的影响。通过节流,我们可以控制滚动回调的执行频率,减少不必要的计算。

节流的应用场景

滚动监听:滚动条位置变化时可能会频繁触发滚动事件,使用节流可以减少事件的触发频率。

窗口调整大小:类似于防抖,节流可以限制窗口调整大小时执行的频率,避免频繁计算布局。

动画帧:在动画过程中,控制动画回调的执行频率,避免在每一帧都做大量计算。

节流的代码

javascript 复制代码
function throttle(func, delay) {  
let lastTime = 0;  
returnfunction (...args) {  
    const now = newDate().getTime();  
    if (now - lastTime >= delay) {  
      func.apply(this, args);  
      lastTime = now;  
    }  
  };  
}  
  
window.addEventListener("scroll", throttle(function () {  
console.log("Scroll event triggered");  
}, 1000));

在这个例子中,我们为scroll事件添加了节流处理,确保每1000毫秒内只执行一次回调函数,从而减少浏览器的性能消耗。

节流的完善代码

javascript 复制代码
const clearTimer = function clearTimer(timer) {
    if (timer) clearTimeout(timer);
    return null;
};


const throttle = function throttle(func, wait) {
    // init params
    if (typeof func !== "function") throw new TypeError("func is not a function!");
    wait = +wait;
    if (isNaN(wait)) wait = 300;
    // handler
    let timer = null,
        previous = 0; //记录上一次func触发的时间
    return function operate(...params) {
        let now = +new Date(),
            remaining = wait - (now - previous);
        if (remaining <= 0) {
            // 两次触发的间隔时间超过设定的频率,则立即执行函数
            func.call(this, ...params);
            previous = +new Date();
            timer = clearTimer(timer);
        } else if (!timer) {
            // 间隔时间不足设定的频率,而且还未设置等待的定时器,则设置定时器等待执行函数即可
            timer = setTimeout(() => {
                func.call(this, ...params);
                previous = +new Date();
                timer = clearTimer(timer);
            }, remaining);
        }
    };
};

三、防抖与节流的区别

简单的应用对比

1.防抖: 适用于需要等待事件结束后执行的操作,比如用户输入结束后的搜索。

2.节流: 适用于需要固定时间间隔内执行的操作,比如滚动监听或定时任务。

四、如何选择防抖和节流?

在实际开发中,选择使用防抖还是节流,要根据具体的需求来决定:

如果事件触发频率非常高,而你只关心最终的执行结果(例如用户输入或按钮点击),那么选择防抖。

如果事件触发频率很高且需要定期执行某些操作(例如滚动监听、游戏中的帧渲染等),那么选择节流。

此外,也可以将两者结合使用,来应对复杂的应用场景。例如,滚动监听可以结合节流处理,而按钮点击事件可以使用防抖来避免多次提交。

五、防抖节流实际应用场景

1. 输入框实时搜索(防抖)

应用场景:在输入框中实时搜索是现代Web应用中常见的功能,用户在输入关键词时,页面会根据输入的内容动态显示搜索结果。如果每次用户输入一个字符都向服务器发送请求,系统会面临大量的请求压力,甚至可能导致页面响应变慢。

解决方案:使用防抖技术,可以避免在每次键盘输入时都发送请求。只有当用户停止输入一段时间后,才会触发请求。

javascript 复制代码
// 防抖函数  
function debounce(func, delay) {  
let timer;  
returnfunction (...args) {  
    if (timer) clearTimeout(timer);  
    timer = setTimeout(() => func.apply(this, args), delay);  
  };  
}  
  
// 输入框实时搜索  
const searchInput = document.querySelector("#search");  
searchInput.addEventListener("keyup", debounce(function () {  
const query = searchInput.value;  
console.log("Searching for:", query); // 这里可以发起真实的网络请求  
}, 500));

效果:在用户停止输入500毫秒后,才会发起一次搜索请求,极大减少了不必要的请求,提升了性能。

2. 页面滚动事件(节流)

应用场景:当用户滚动页面时,scroll事件会频繁触发。如果不加控制,每次触发scroll事件都执行一个耗时的操作(例如,加载更多数据或计算滚动位置),可能导致页面卡顿,影响用户体验。

解决方案:使用节流技术,限制滚动事件处理函数的执行频率,避免每次滚动时都触发大量计算。

javascript 复制代码
// 节流函数  
function throttle(func, delay) {  
let lastTime = 0;  
returnfunction (...args) {  
    const now = newDate().getTime();  
    if (now - lastTime >= delay) {  
      func.apply(this, args);  
      lastTime = now;  
    }  
  };  
}  
  
// 页面滚动加载更多  
window.addEventListener("scroll", throttle(function () {  
const scrollPosition = window.scrollY + window.innerHeight;  
const documentHeight = document.documentElement.scrollHeight;  
  
// 判断是否接近页面底部,进行数据加载  
if (scrollPosition >= documentHeight - 100) {  
    console.log("Loading more content...");  
    // 发起加载更多数据的请求  
  }  
}, 200)); // 每200ms执行一次 

效果:即使用户快速滚动页面,也会限制scroll事件处理函数的调用频率,每200ms才执行一次,减少不必要的计算和DOM操作。

效果:即使用户快速滚动页面,也会限制scroll事件处理函数的调用频率,每200ms才执行一次,减少不必要的计算和DOM操作。

3. 窗口调整大小(节流)

应用场景:当用户调整浏览器窗口大小时,resize事件会频繁触发。如果在每次触发时进行重计算(如重新布局、调整元素大小等),可能导致页面性能下降,特别是在响应式设计的页面中。

解决方案:使用节流技术,减少resize事件处理的频率,避免重复执行布局计算。

javascript 复制代码
// 节流函数  
function throttle(func, delay) {  
let lastTime = 0;  
returnfunction (...args) {  
    const now = newDate().getTime();  
    if (now - lastTime >= delay) {  
      func.apply(this, args);  
      lastTime = now;  
    }  
  };  
}  
  
// 监听窗口调整大小  
window.addEventListener("resize", throttle(function () {  
console.log("Window resized:", window.innerWidth, window.innerHeight);  
// 执行需要重新计算的逻辑,如重新调整布局  
}, 300)); // 每300ms执行一次

效果:通过节流技术,确保在窗口调整大小时不进行过多的计算,从而提高页面的流畅度。

4. 表单提交按钮防止重复点击(防抖)

应用场景:在一些表单提交场景中,用户可能在短时间内多次点击提交按钮,导致多次请求发出,甚至出现重复提交。常见的场景如支付、注册等。

解决方案:使用防抖技术,防止用户短时间内多次点击按钮,确保表单只提交一次。

javascript 复制代码
// 防抖函数  
function debounce(func, delay) {  
let timer;  
returnfunction (...args) {  
    if (timer) clearTimeout(timer);  
    timer = setTimeout(() => func.apply(this, args), delay);  
  };  
}  
  
// 防止重复提交表单  
const submitButton = document.querySelector("#submitBtn");  
submitButton.addEventListener("click", debounce(function () {  
console.log("Form submitted!");  
// 发送表单请求  
// submitForm();  
}, 1000)); // 1000ms内只触发一次提交

效果:即使用户快速多次点击按钮,也只会触发一次提交操作,防止重复提交。

5. 图片懒加载(节流)

应用场景:在长列表或长页面中,图片通常会根据用户滚动加载。scroll事件的触发频率可能非常高,如果每次滚动都计算图片是否需要加载,可能会造成性能问题。

解决方案:使用节流控制滚动事件的处理频率,确保只在合适的时间点计算哪些图片需要加载。

javascript 复制代码
// 节流函数   
function throttle(func, delay) {   
let lastTime = 0;   
returnfunction (...args) {   
    const now = newDate().getTime();   
    if (now - lastTime >= delay) {   
      func.apply(this, args);   
      lastTime = now;   
}  
};  
}   
// 图片懒加载   
const images = document.querySelectorAll("img.lazyload");   
  
function lazyLoadImages() {   
  images.forEach(img => {   
    if (img.getBoundingClientRect().top < window.innerHeight && !img.src) {   
      img.src = img.dataset.src; // 从data-src属性加载图片   
      img.classList.remove("lazyload");   
}   
});  
}  
window.addEventListener("scroll", throttle(lazyLoadImages, 300)); // 每300ms触发一次

效果:通过节流,减少每次滚动时对图片的加载检查,确保只有在需要加载图片时才触发计算,优化页面性能。

6. 表单验证(防抖)

应用场景:用户在表单中输入数据时,需要进行实时的字段验证。如果每次用户输入时都触发验证逻辑,会导致页面性能下降,尤其是在多个输入字段的情况下。

解决方案:使用防抖,确保只有在用户停止输入一段时间后才进行表单验证,从而减少不必要的验证请求。

javascript 复制代码
// 防抖函数   
function debounce(func, delay) {   
let timer;   
returnfunction (...args) {   
    if (timer) clearTimeout(timer);  
    timer = setTimeout(() => func.apply(this, args), delay);  
};  
  }   
  
// 实时验证用户名   
const usernameInput = document.querySelector("#username");   
usernameInput.addEventListener("input", debounce(function () {   
const username = usernameInput.value;   
console.log("Validating username:", username);   
// 发起验证请求   
}, 500)); // 输入停止500ms后触发验证

效果:防止在每次用户输入时都进行验证,只在用户停止输入500毫秒后触发一次验证,提升页面响应速度。

六、总结

防抖:适用于需要等待事件完成后执行一次的场景。

节流:适用于需要限制执行频率的场景。

相关推荐
好_快1 分钟前
Lodash源码阅读-getSymbolsIn
前端·javascript·源码阅读
好_快2 分钟前
Lodash源码阅读-baseAssignValue
前端·javascript·源码阅读
好_快2 分钟前
Lodash源码阅读-copyObject
前端·javascript·源码阅读
猫猫不是喵喵.8 小时前
vue 路由
前端·javascript·vue.js
烛阴8 小时前
JavaScript Import/Export:告别混乱,拥抱模块化!
前端·javascript
bin91539 小时前
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加行拖拽排序功能示例12,TableView16_12 拖拽动画示例
前端·javascript·vue.js·ecmascript·deepseek
牛马baby9 小时前
Java高频面试之并发编程-01
java·开发语言·面试
拉不动的猪9 小时前
vue自定义“权限控制”指令
前端·javascript·vue.js
拉不动的猪10 小时前
刷刷题48 (setState常规问答)
前端·react.js·面试
魔云连洲10 小时前
Vue2和Vue3响应式的基本实现
开发语言·前端·javascript·vue.js