用一段20行代码,彻底搞懂节流:窗口resize时的背景变色实验

1,282 阅读7分钟

前言

最近做前端页面时,遇到一个挺有意思的需求:用户调整窗口大小时,页面背景色随机变化。本以为是个简单功能,结果测试时发现:当用户拖动窗口边缘快速调整大小时,背景色疯狂闪烁——1秒内竟变了10几次!这不仅亮瞎了我的钛合金眼,更导致浏览器CPU占用飙升。之前写了一篇关于防抖的博客,今天就讲讲它的好哥们「节流(Throttle)」

今天就用这个案例,带大家从问题出发,通过一段20行的代码,彻底搞懂节流的核心逻辑和实际应用。

问题复现:没有节流的「疯狂变色」

先看最初的代码(去掉节流的版本):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>无节流的背景变色</title>
</head>
<body>
    <script>
        function coloring() {
            // 生成0-255的随机RGB值
            const r = Math.floor(Math.random() * 255);
            const g = Math.floor(Math.random() * 255);
            const b = Math.floor(Math.random() * 255);
            // 设置背景色
            document.body.style.background = `rgb(${r},${g},${b})`;
        }
        // 直接绑定resize事件
        window.addEventListener('resize', coloring);
    </script>
</body>
</html>

打开这个页面,尝试快速拖动窗口边缘调整大小——你会看到背景色像走马灯一样疯狂变化。用浏览器的开发者工具(F12)查看resize事件的触发频率,发现1秒内竟触发了20-30次

这是因为resize事件是典型的高频事件:只要窗口尺寸变化,浏览器就会持续触发该事件。如果直接绑定函数,会导致函数高频执行,造成两个问题:

  1. 性能问题:频繁修改DOM样式(背景色)会触发重绘,增加浏览器负担
  2. 用户体验差:背景色闪烁会让用户感到眩晕,尤其是对视觉敏感的人群

屏幕录制+2025-05-12+135946.gif

节流的核心:给事件加个「频率限制器」

要解决这个问题,关键是控制coloring函数的执行频率。这时候,节流(Throttle)就派上用场了。节流的核心逻辑是:无论事件触发多频繁,保证目标函数在固定时间内最多执行一次

举个生活化的例子:你家的热水器——不管你多频繁地开关水龙头(高频事件),热水器只会每隔1秒加热一次(固定频率执行)。这样既保证了水温稳定,又避免了热水器过载。

代码实现:20行写出时间戳版节流函数

理解原理后,我们来自己实现节流函数。用户提供的代码中用了「时间戳版」的节流实现,这是最经典的方式之一:

1. 先看完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流版背景变色</title>
</head>
<body>
    <script>
        // 核心功能:生成随机背景色
        function coloring() {
            const r = Math.floor(Math.random() * 255);
            const g = Math.floor(Math.random() * 255);
            const b = Math.floor(Math.random() * 255);
            document.body.style.background = `rgb(${r},${g},${b})`;
        }

        // 节流函数实现(时间戳版)
        function throttle(func, delay) {
            let prevTime = 0; // 记录上一次执行的时间戳
            return function() {
                const currentTime = Date.now(); // 当前时间戳
                // 如果当前时间与上次执行时间间隔超过delay,执行函数
                if (currentTime - prevTime >= delay) {
                    func(); // 执行目标函数
                    prevTime = currentTime; // 更新上次执行时间
                }
            };
        }

        // 绑定resize事件,使用节流(延迟1000ms)
        window.addEventListener('resize', throttle(coloring, 1000));
    </script>
</body>
</html>

2. 逐行解析核心代码

  • coloring函数
    负责生成随机RGB值并设置背景色。Math.floor(Math.random()*255)会生成0-255的整数(包括0,不包括255),确保每个颜色通道值有效。

  • throttle函数

    • prevTime:闭包中保存的变量,记录上一次执行func的时间戳。初始值为0,保证第一次触发时立即执行。
    • currentTime:每次事件触发时获取当前时间戳(精确到毫秒)。
    • 条件判断currentTime - prevTime >= delay:如果当前时间与上次执行时间的间隔超过设定的delay(本例中是1000ms),则执行func并更新prevTime
  • 事件绑定
    window.addEventListener('resize', throttle(coloring, 1000))resize事件绑定到节流后的coloring函数,意味着无论用户多快调整窗口大小,coloring最多每秒执行一次。

屏幕录制+2025-05-12+135701.gif

效果对比:用事实说话

为了验证节流效果,我们可以在coloring函数中添加日志:

function coloring() {
    const r = Math.floor(Math.random() * 255);
    const g = Math.floor(Math.random() * 255);
    const b = Math.floor(Math.random() * 255);
    document.body.style.background = `rgb(${r},${g},${b})`;
    console.log(`背景色变化,时间:${new Date().toLocaleTimeString()}`);
}

无节流时:

  • 快速拖动窗口1秒,控制台会输出20-30条日志(对应20-30次背景色变化)。
  • 浏览器的性能面板(Performance标签)显示,resize事件处理函数的执行时间占比高达30%以上。

有节流(1000ms)时:

  • 同样快速拖动窗口1秒,控制台最多输出1条日志(每秒最多执行一次)。
  • 即使持续拖动窗口5秒,控制台也只会输出5条日志(每秒一次)。
  • 性能面板显示,resize事件处理函数的执行时间占比降至5%以下,页面滚动更流畅。

节流的其他实现方式:时间戳vs定时器

用户提供的代码用了「时间戳版」节流,实际开发中还有另一种常见的「定时器版」实现。我们来对比两者的差异:

1. 时间戳版(上面例子)

function throttle(func, delay) {
    let prevTime = 0;
    return function() {
        const currentTime = Date.now();
        if (currentTime - prevTime >= delay) {
            func();
            prevTime = currentTime;
        }
    };
}

特点

  • 第一次触发事件时立即执行(因为prevTime初始为0,currentTime - 0肯定大于delay)。
  • 最后一次触发事件后,不会补执行(比如用户停止拖动窗口,不会再触发一次)。

2. 定时器版

function throttle(func, delay) {
    let timer = null;
    return function() {
        if (!timer) {
            timer = setTimeout(() => {
                func();
                timer = null; // 执行后清空定时器
            }, delay);
        }
    };
}

特点

  • 第一次触发事件时,延迟delay时间后执行(因为需要等待定时器触发)。
  • 最后一次触发事件后,会补执行一次(定时器会在delay时间后触发)。

3. 综合版:鱼和熊掌兼得

实际开发中,我们可能希望:第一次触发立即执行,最后一次触发也补执行。这时候可以结合时间戳和定时器:

function throttle(func, delay) {
    let prevTime = 0;
    let timer = null;
    return function() {
        const currentTime = Date.now();
        // 如果距离上次执行超过delay,立即执行(时间戳逻辑)
        if (currentTime - prevTime >= delay) {
            // 如果有未完成的定时器,先取消
            if (timer) {
                clearTimeout(timer);
                timer = null;
            }
            func();
            prevTime = currentTime;
        } 
        // 否则设置定时器,保证最后一次触发会执行(定时器逻辑)
        else if (!timer) {
            timer = setTimeout(() => {
                func();
                prevTime = Date.now();
                timer = null;
            }, delay - (currentTime - prevTime)); // 计算剩余等待时间
        }
    };
}

特点

  • 第一次触发立即执行。
  • 中间高频触发时,每隔delay时间执行一次。
  • 最后一次触发后,会在剩余时间内补执行一次。

节流的应用场景:不止是背景变色

通过这个背景变色的案例,我们能直观感受到节流的价值:将高频事件转化为低频执行,平衡性能与体验。除了resize事件,节流在以下场景也非常实用:

1. 滚动加载更多

当用户滚动页面时,scroll事件会高频触发。使用节流可以保证每隔300ms检测一次是否滚动到页面底部,避免频繁请求接口。

2. 游戏技能冷却

游戏中,技能释放后需要冷却时间(比如2秒)。节流可以保证技能在冷却时间内无法重复释放,避免玩家无限连放。

3. 窗口resize计算布局

调整窗口大小时,需要重新计算元素布局(比如网格列数)。使用节流可以避免频繁计算,提升页面流畅度。

总结:节流的核心价值与最佳实践

通过这个背景变色的小实验,我们不仅学会了节流的代码实现,更理解了它的核心价值:在高频事件中,控制目标函数的执行频率,既保证用户体验,又降低性能消耗

核心总结

  • 节流的本质:给高频事件加一个「频率限制器」,固定时间内最多执行一次。
  • 时间戳版:立即执行,无收尾补执行;定时器版:延迟执行,有收尾补执行;综合版:两者兼顾。
  • 适用场景:需要定期更新状态的高频事件(如resizescroll、游戏帧更新)。

最佳实践建议

  1. 合理设置延迟时间:根据业务场景测试调整。resizescroll通常用100-300ms,游戏技能冷却用1000ms以上。
  2. 处理事件参数:如果目标函数需要事件对象(如event.target),记得用arguments传递参数(参考function throttle(func, delay) { ... }的综合版实现)。
  3. 使用成熟库:如果项目允许,推荐使用lodash.throttle,它处理了this指向、参数传递、取消功能等边缘情况。
  4. 结合防抖使用:防抖(Debounce)适合「停止操作后执行最终结果」的场景(如搜索输入),节流适合「定期执行」的场景,两者互补。

最后想说,前端开发中,很多看似简单的功能(比如背景变色)背后都隐藏着性能优化的学问。理解节流这样的基础概念,不仅能解决具体问题,更能培养「从用户体验出发,从性能细节入手」的技术思维。

下次遇到高频事件问题时,不妨试试节流——你会发现,控制频率,反而能让交互更流畅。如果本文对你有帮助,欢迎点赞收藏,评论区聊聊你遇到的高频事件优化案例~