那些年我们忽略的高频事件,正在拖垮你的页面

在前端开发中,我们经常需要处理一些高频触发的事件,比如:

  • 输入框搜索建议(inputkeyup
  • 窗口调整大小(resize
  • 滚动事件(scroll
  • 鼠标移动(mousemove

这些事件如果每次都执行某些代价较高的操作(如发起网络请求、重排重绘页面等),会对性能造成严重影响。为了解决这个问题,我们可以使用 防抖(debounce)节流(throttle) 技术。


🧠 一、什么是防抖(Debounce)?

定义:

防抖 是指,在一段时间内多次触发同一个函数,只有最后一次触发后经过指定时间没有再次触发,才会真正执行该函数。

类比:

就像你在打字时,搜索引擎不会每次按键都去请求服务器,而是等你停下来后再请求。 就像你打游戏的回城,自己移动了或被打断了就只能重新回城,直到停下来等了指定时间才会真正回城成功。

应用场景:

  • 搜索框输入实时建议
  • 表单验证
  • 窗口调整尺寸
  • 多次点击按钮(避免重复提交)

✅ 示例代码:

javascript 复制代码
function debounce(fn, delay) {
    return function (args) {
        const that = this;
        clearTimeout(fn.id);
        fn.id = setTimeout(() => {
            fn.call(that, args); // 保留 this 上下文
        }, delay);
    };
}

📌 使用示例:

javascript 复制代码
const inputA = document.getElementById('inputA');

inputA.addEventListener('keyup', debounce(function(e) {
    console.log('发送请求:', e.target.value);
}, 300));

🧠 二、什么是节流(Throttle)?

定义:

节流 是指,在一定时间间隔内只允许执行一次函数。无论在这段时间内触发多少次,函数只会执行一次。

类比:

就像游戏中的技能冷却,每 5 秒只能释放一次技能。

应用场景:

  • 页面滚动加载更多内容(如瀑布流)
  • 窗口调整大小(避免频繁布局计算)
  • 实时位置更新(如地图定位)

✅ 示例代码:

javascript 复制代码
function throttle(fn, delay) {
    let last = 0;

    return function (...args) {
        const now = +new Date();
        if (!last || now - last >= delay) {
            fn.apply(this, args);
            last = now;
        }
    };
}

📌 使用示例:

javascript 复制代码
window.addEventListener('resize', throttle(function() {
    console.log('窗口大小变化');
}, 200));

🔁 三、防抖 vs 节流:对比总结

特性 防抖(Debounce) 节流(Throttle)
原理 在规定时间内未被再次触发才执行 固定时间只执行一次
触发频率 多次触发 → 只执行最后一次 多次触发 → 每隔一段时间执行一次
典型用途 搜索建议、表单校验 滚动监听、窗口调整、动画帧控制

💡 四、进阶技巧与常见问题

1. 如何保证 this 不丢失?

在对象方法或事件回调中,this 很容易指向全局对象(如 window)。解决方案有:

✅ 方法一:用变量保存 this

javascript 复制代码
obj.inc = debounce(function(val) {
    const that = this;
    setTimeout(function () {
        that.count += val;
    }, 500);
});

✅ 方法二:使用 .bind()

fn.bind(that)(args);

✅ 方法三:使用箭头函数(推荐)

javascript 复制代码
setTimeout(() => {
    fn(args);
}, delay);

箭头函数不绑定自己的 this,继承外层函数的 this,非常适用于事件处理和定时器。


2. 如何兼容箭头函数和普通函数?

如果你传入的是一个箭头函数作为 fn,也要注意它的 this 绑定行为。通常我们会优先使用箭头函数来简化上下文管理。


3. 如何封装成可复用的工具函数?

你可以将防抖和节流函数封装到一个通用工具库中,例如:

javascript 复制代码
// utils.js
export function debounce(fn, delay) {
    let timer;
    return function (...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

export function throttle(fn, delay) {
    let last = 0;
    return function (...args) {
        const now = +new Date();
        if (now - last > delay) {
            fn.apply(this, args);
            last = now;
        }
    };
}

然后在组件中导入使用:

javascript 复制代码
import { debounce } from './utils';

input.addEventListener('input', debounce(fetchSuggestions, 300));

📈 五、实际应用案例分析

1. 输入框搜索建议(防抖)

html 复制代码
<input type="text" id="searchInput">
<script>
    const input = document.getElementById('searchInput');

    function fetchSuggestions(query) {
        console.log('发送请求:', query);
    }

    input.addEventListener('input', debounce((e) => {
        fetchSuggestions(e.target.value);
    }, 300));
</script>

2. 图片懒加载 + 滚动监听(节流)

html 复制代码
<img data-src="image1.jpg" class="lazy-img">
<script>
    function lazyLoadImages() {
        const images = document.querySelectorAll('.lazy-img');
        images.forEach(img => {
            if (isInViewport(img)) {
                img.src = img.dataset.src;
            }
        });
    }

    window.addEventListener('scroll', throttle(lazyLoadImages, 200));
</script>

关于图片懒加载,想详细了解可看前端性能优化实战:一文搞懂图片懒加载(Lazy Load),从原理到代码全解析在图片泛滥的网页时代,加载速度决定用户体验 - 掘金


🧩 六、拓展知识:高阶函数 & 闭包的应用

1. 高阶函数(Higher-order Function)

防抖和节流函数都是典型的高阶函数,它们:

  • 接收一个函数作为参数
  • 返回一个新的函数(包装后的函数)

这种设计模式广泛应用于现代 JS 开发中,尤其在 React、Vue 等框架中。

2. 闭包(Closure)

在防抖和节流函数中,我们利用了闭包来保存状态(如 timerlastfn.id 等),这使得函数可以在多次调用之间共享状态,而不污染全局作用域。


🎯 七、结语:何时用防抖?何时用节流?

场景 推荐技术
用户输入搜索建议 ✅ 防抖
窗口调整大小 ✅ 节流
滚动加载更多内容 ✅ 节流
频繁点击按钮 ✅ 防抖
实时数据同步(如聊天输入) ✅ 防抖
动画帧控制 ✅ 节流

📚 参考资料


相关推荐
风吹落叶花飘荡43 分钟前
2025 Next.js项目提前编译并在服务器
服务器·开发语言·javascript
yanlele1 小时前
我用爬虫抓取了 25 年 6 月掘金热门面试文章
前端·javascript·面试
花好月圆春祺夏安2 小时前
基于odoo17的设计模式详解---命令模式
设计模式·命令模式
烛阴2 小时前
WebSocket实时通信入门到实践
前端·javascript
草巾冒小子2 小时前
vue3实战:.ts文件中的interface定义与抛出、其他文件的调用方式
前端·javascript·vue.js
追逐时光者2 小时前
面试第一步,先准备一份简洁、优雅的简历模板!
后端·面试
DoraBigHead2 小时前
你写前端按钮,他们扛服务器压力:搞懂后端那些“黑话”!
前端·javascript·架构
前端世界3 小时前
鸿蒙UI开发全解:JS与Java双引擎实战指南
javascript·ui·harmonyos
@Dream_Chaser4 小时前
uniapp ruoyi-app 中使用checkbox 无法选中问题
前端·javascript·uni-app
上单带刀不带妹4 小时前
JavaScript中的Request详解:掌握Fetch API与XMLHttpRequest
开发语言·前端·javascript·ecmascript