【前端面试小册】JS-第25节:防抖节流进阶与实战

一、概述

1.1 核心概念

函数防抖和函数节流都是防止某一时间频繁触发,但是这两兄弟之间的原理却不一样。

  • 函数防抖(debounce):某一段时间内只执行一次,在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时
  • 函数节流(throttle):间隔时间执行,规定在一个单位时间内,只能触发一次函数

类比理解

  • 防抖:就像电梯门,有人进来就重新计时,直到没人进来才关门
  • 节流:就像水龙头,无论怎么拧,单位时间内流出的水量是固定的

1.2 区别对比

特性 防抖(debounce) 节流(throttle)
执行时机 停止触发后执行 固定时间间隔执行
适用场景 搜索框输入、窗口 resize 滚动事件、鼠标移动
效果 频繁触发只执行最后一次 频繁触发按固定频率执行

二、防抖(debounce)实现

2.1 基础实现

function debounce(fn, delay, immediate) {
    let timer = null;
    return function (...args) {
        let context = this;
        
        // 清除之前的定时器
        if (timer) clearTimeout(timer);
        
        if (immediate) {
            // 立即执行模式
            let doNow = !timer;
            timer = setTimeout(() => {
                timer = null;
            }, delay);
            if (doNow) {
                fn.apply(context, args);
            }
        } else {
            // 延迟执行模式
            timer = setTimeout(() => {
                fn.apply(context, args);
                timer = null;
            }, delay);
        }
    };
}

2.2 执行流程

graph TD
    A[触发事件] --> B{immediate模式?}
    B -->|是| C{是否有timer?}
    B -->|否| D[清除旧timer]
    C -->|否| E[立即执行]
    C -->|是| F[不执行]
    E --> G[设置新timer]
    F --> G
    D --> H[设置新timer]
    H --> I[延迟后执行]
    G --> J[延迟后清除timer]

2.3 使用示例

// 基础防抖
const debouncedFn = debounce(() => {
    console.log('执行了');
}, 1000);

// 立即执行模式
const debouncedFnImmediate = debounce(() => {
    console.log('立即执行');
}, 1000, true);

// 搜索框示例
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
    console.log('搜索:', e.target.value);
}, 500));

2.4 增强版本(支持取消)

function debounce(fn, delay, immediate) {
    let timer = null;
    
    const debounced = function (...args) {
        let context = this;
        
        if (timer) clearTimeout(timer);
        
        if (immediate) {
            let doNow = !timer;
            timer = setTimeout(() => {
                timer = null;
            }, delay);
            if (doNow) {
                fn.apply(context, args);
            }
        } else {
            timer = setTimeout(() => {
                fn.apply(context, args);
                timer = null;
            }, delay);
        }
    };
    
    // 取消功能
    debounced.cancel = function() {
        if (timer) {
            clearTimeout(timer);
            timer = null;
        }
    };
    
    return debounced;
}

三、节流(throttle)实现

3.1 时间戳版本(立即执行)

// 时间戳立即执行一次
function throttle(fn, time) {
    let pre = 0;
    return function (...args) {
        let now = Date.now();
        if (now - pre > time) {
            fn.apply(this, args);
            pre = now;
        }
    };
}

特点

  • 第一次触发立即执行
  • 停止触发后不再执行

3.2 定时器版本(延迟执行)

// 定时器版本
function throttle(fn, time) {
    let timer = null;
    return function (...args) {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, args);
                timer = null;
            }, time);
        }
    };
}

特点

  • 第一次触发延迟执行
  • 停止触发后还会执行一次

3.3 完整版本(结合两者优点)

function throttle(fn, threshhold, scope) {
    threshhold || (threshhold = 250);
    var last,
        timer;
    return function () {
        var context = scope || this;
        var now = +new Date(),
            args = arguments;
        if (last && now < last + threshhold) {
            // 如果距离上次执行的时间小于设定的时间周期,则放弃执行
            clearTimeout(timer);
            timer = setTimeout(function () {
                last = now;
                fn.apply(context, args);
            }, threshhold);
        } else {
            // 如果时间周期已经过了,则执行函数
            last = now;
            fn.apply(context, args);
        }
    };
}

特点

  • 第一次触发立即执行
  • 停止触发后还会执行一次
  • 结合了时间戳和定时器的优点

3.4 使用示例

// 基础节流
function myFunction() {
    console.log('Function called!');
}

var myThrottledFunction = throttle(myFunction, 1000);
window.addEventListener('resize', myThrottledFunction);

// 滚动事件示例
window.addEventListener('scroll', throttle(() => {
    console.log('滚动中');
}, 200));

四、应用场景

4.1 防抖(debounce)应用场景

搜索框输入

// 输入框,不断输入值时,用防抖来节约请求资源
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
    const keyword = e.target.value;
    // 发送搜索请求
    fetch(`/api/search?q=${keyword}`)
        .then(res => res.json())
        .then(data => {
            // 更新搜索结果
            updateSearchResults(data);
        });
}, 500));

窗口 resize

// window 触发 resize 的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
window.addEventListener('resize', debounce(() => {
    console.log('窗口大小改变');
    // 重新计算布局
    recalculateLayout();
}, 300));

按钮提交

// 防止用户重复提交
const submitBtn = document.getElementById('submit');
submitBtn.addEventListener('click', debounce(() => {
    submitForm();
}, 1000, true));  // 立即执行,防止重复点击

4.2 节流(throttle)应用场景

鼠标点击

// 鼠标不断点击触发,mousedown(单位时间内只触发一次)
document.addEventListener('mousedown', throttle(() => {
    console.log('鼠标点击');
}, 1000));

滚动事件

// 监听滚动事件,比如是否滑到底部自动加载更多,用 throttle 来判断
window.addEventListener('scroll', throttle(() => {
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    const windowHeight = window.innerHeight;
    const documentHeight = document.documentElement.scrollHeight;
    
    if (scrollTop + windowHeight >= documentHeight - 100) {
        // 加载更多
        loadMore();
    }
}, 200));

鼠标移动

// 鼠标移动事件
document.addEventListener('mousemove', throttle((e) => {
    console.log('鼠标位置:', e.clientX, e.clientY);
}, 100));

五、实际项目应用

5.1 React Hook 版本

import { useRef, useCallback } from 'react';

// 防抖 Hook
function useDebounce(fn, delay) {
    const timerRef = useRef(null);
    
    const debouncedFn = useCallback((...args) => {
        if (timerRef.current) {
            clearTimeout(timerRef.current);
        }
        timerRef.current = setTimeout(() => {
            fn(...args);
        }, delay);
    }, [fn, delay]);
    
    return debouncedFn;
}

// 节流 Hook
function useThrottle(fn, delay) {
    const lastRun = useRef(Date.now());
    
    const throttledFn = useCallback((...args) => {
        if (Date.now() - lastRun.current >= delay) {
            fn(...args);
            lastRun.current = Date.now();
        }
    }, [fn, delay]);
    
    return throttledFn;
}

5.2 Vue 指令版本

// 防抖指令
Vue.directive('debounce', {
    inserted(el, binding) {
        let timer = null;
        el.addEventListener('click', () => {
            if (timer) clearTimeout(timer);
            timer = setTimeout(() => {
                binding.value();
            }, binding.arg || 1000);
        });
    }
});

// 节流指令
Vue.directive('throttle', {
    inserted(el, binding) {
        let lastTime = 0;
        el.addEventListener('click', () => {
            const now = Date.now();
            if (now - lastTime >= (binding.arg || 1000)) {
                binding.value();
                lastTime = now;
            }
        });
    }
});

六、性能优化

6.1 内存泄漏防护

function debounce(fn, delay, immediate) {
    let timer = null;
    
    const debounced = function (...args) {
        // ... 实现代码
    };
    
    // 清理函数
    debounced.cancel = function() {
        if (timer) {
            clearTimeout(timer);
            timer = null;
        }
    };
    
    return debounced;
}

// 使用示例
const debouncedFn = debounce(() => {
    console.log('执行');
}, 1000);

// 组件卸载时清理
// React: useEffect cleanup
// Vue: beforeDestroy
debouncedFn.cancel();

6.2 参数传递优化

// 使用箭头函数保持 this 指向
function debounce(fn, delay) {
    let timer = null;
    return (...args) => {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn(...args);
        }, delay);
    };
}

七、面试要点总结

核心知识点

  1. 防抖:停止触发后执行,适用于搜索、resize
  2. 节流:固定频率执行,适用于滚动、鼠标移动
  3. 实现方式:时间戳、定时器、结合版本
  4. 应用场景:根据实际需求选择防抖或节流

常见面试题

Q1: 防抖和节流的区别?

答:

  • 防抖:停止触发后执行,频繁触发只执行最后一次
  • 节流:固定频率执行,频繁触发按固定频率执行

Q2: 如何实现防抖?

答:使用 setTimeout,每次触发清除旧定时器,设置新定时器。支持立即执行模式。

Q3: 如何实现节流?

答:可以使用时间戳(立即执行)或定时器(延迟执行),或结合两者优点。

实战建议

  • ✅ 理解防抖和节流的区别和适用场景
  • ✅ 掌握基础实现和增强版本
  • ✅ 注意内存泄漏问题(提供取消方法)
  • ✅ 在实际项目中合理使用,提升性能
#前端面试##前端##百度##滴滴##阿里#
前端面试小册 文章被收录于专栏

每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

全部评论

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务