JavaScript 深入理解节流与防抖

作者:青柠

时间:2026-10-25

浏览:130

本文深入解析 JavaScript 中防抖(Debounce)与节流(Throttle)的核心原理与实现。针对 scroll、resize 等高频触发事件,通过合并或限制执行频率来降低浏览器负担,避免卡帧。文章详细对比了两种技术的适用场景、实现代码及演化过程,帮助开发者提升 Web 页面渲染性能与用户体验。

文章目录

一、js防抖和节流

在处理窗口 resize、scroll 滚动或输入框内容校验等操作时,若事件处理函数无限制地高频调用,会显著增加浏览器负担,导致用户体验严重下降。为了优化前端性能并提升用户交互体验,通常采用防抖(debounce)和节流(throttle)策略来降低函数调用的频率。

二、为什么滚动scroll、窗口resize等事件需要优化

滚动事件的应用场景非常广泛,例如图片懒加载、下滑自动加载数据以及侧边浮动导航栏等。

在绑定 scroll 或 resize 事件时,其触发频率极高且间隔极短。在常规页面开发中,若事件处理涉及大量位置计算、DOM 操作或元素重绘,且这些操作无法在下一个 scroll 事件触发前完成,浏览器就会出现明显的卡顿现象。

三、滚动和页面渲染前端性能优化的关系

为何需要优化滚动事件?前文提到事件中涉及大量位置计算、DOM 操作和元素重绘,这些操作直接关联页面渲染,而页面渲染效率又决定了前端整体性能。这几者层层嵌套,联系紧密,每一环节都至关重要。

review一下前端的渲染和优化

Web 页面的展示经历以下步骤:js—style—layout—paint—composite。此处仅做简单回顾,不进行详细介绍。

网页生成时,至少会执行一次渲染(Layout+Paint)。在用户访问过程中,页面会不断进行重排(reflow)和重绘(repaint)。用户的 scroll 和 resize 行为会频繁触发页面重新渲染,若间隔过密,极易导致浏览器卡帧。

四、防抖Debounce

1 防抖Debounce情景

①某些场景下事件触发的频率过高,例如 mousemove、onkeydown、onkeyup、onscroll 等。

②若回调函数执行频率过高,也会引发卡顿。可以通过设置延迟触发来去除无效操作。

2 防抖原理

事件必须在触发 n 秒后才执行;若在 n 秒内再次触发该事件,则以新触发时间为准,重新计算 n 秒后执行。只有当触发事件后 n 秒内不再次触发,才会真正执行。

官方解释:当持续触发事件时,若指定时间段内未再触发事件,事件处理函数才会执行一次;若在该时间段到来前再次触发,则重新开始延时。

3 防抖函数简单实现

    //简单的防抖函数
    function debounce(func, wait, immediate) {
        //定时器变量
        var timeout;
        return function () {
            //每次触发scrolle,先清除定时器
            clearTimeout(timeout);
            //指定多少秒后触发事件操作handler
            timeout = setTimeout(func, wait);
        };
    };
    //绑定在scrolle事件上的handler
    function handlerFunc() {
        console.log('Success');
    }
    //没采用防抖动
    window.addEventListener('scroll', handlerFunc);
    //采用防抖动
    window.addEventListener('scrolle', debounce(handlerFunc, 1000));

4 防抖函数的演化过程

①this event绑定问题

    //以闭包的形式返回一个函数,内部解决了this指向的问题,event对象传递的问题
    function debounce(func, wait) {
        var timeout;
        return function () {
            var context = this;
            var args = arguments;
            clearTimeout(timeout)
            timeout = setTimeout(function () {
                func.apply(context, args)
            }, wait);
        };
    };

②立即触发问题

    //首次触发执行,再次触发以后开始执行防抖函数
    //使用的时候不用重复执行这个函数,本身返回的函数才具有防抖功能
function debounce(func, wait, immediate) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        
        if(timeout) clearTimeout(timeout);
        // 是否在某一批事件中首次执行
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(function() {
                timeout = null;
                func.apply(context, args)
                immediate = true;
            }, wait);
            if (callNow) {
                func.apply(context, args)
            }
            immediate = false;
        } else {
            timeout = setTimeout(function() {
                func.apply(context, args);
                immediate = true;
            }, wait);
        }
    }
}

③返回值问题

function debounce(func, wait, immediate) {
    var timeout, result;
    return function () {
        var context = this, args = arguments;
        if (timeout)  clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(function() {
                result = func.apply(context, args)
            }, wait);
            if (callNow) result = func.apply(context, args);
        } else {
            timeout = setTimeout(function() {
                result = func.apply(context, args)
            }, wait);
        }
        return result;
    }
}

④取消防抖,添加cancel方法

function debounce(func, wait, immediate) {
    var timeout, result;
    function debounced () {
        var context = this, args = arguments;
        if (timeout)  clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(function() {
                result = func.apply(context, args)
            }, wait);
            if (callNow) result = func.apply(context, args);
        } else {
            timeout = setTimeout(function() {
                result = func.apply(context, args)
            }, wait);
        }
        return result;
    }
    debounced.cancel = function(){
        cleatTimeout(timeout);
        timeout = null;
    }
    return debounced;
}

五、节流Throttle

1 节流Throttle情景

①图片懒加载

②ajax数据请求加载

2 节流原理

若持续触发事件,每隔一段时间只执行一次函数。

官方解释:当持续触发事件时,保证在指定时间段内只调用一次事件处理函数。

3 节流实现—时间戳和定时器

时间戳

    var throttle = function (func, delay) {
        var prev = Date.now()
        return function () {
            var context = this;
            var args = arguments;
            var now = Date.now();
            if (now - prev >= delay) {
                func.apply(context, args);
                prev = Date.now();
            }
        }
    }

    function handle() {
        console.log(Math.random());
    }
    window.addEventListener('scroll', throttle(handle, 1000));

定时器

    var throttle = function (func, delay) {
        var timer = null;
        return function () {
            var context = this;
            var args = arguments;
            if (!timer) {
                timer = setTimeout(function () {
                    func.apply(context, args);
                    timer = null;
                }, delay);
            }
        }
    }

    function handle() {
        console.log(Math.random());
    }
    window.addEventListener('scroll', throttle(handle, 1000))

4 节流函数的演化过程

①时间戳触发

//在开始触发时,会立即执行一次; 如果最后一次没有超过 wait 值,则不触发
function throttle(func, wait) {
    var context, args;
    var previous = 0; // 初始的时间点,也是关键的时间点

    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

②定时器触发

//首次不会立即执行,最后一次会执行,和用时间戳的写法互补。
function throttle(func, wait){
    var context, args, timeout;
    return function() {
        context = this;
        args = arguments;
        if(!timeout) {
            timeout = setTimeout(function(){
                func.apply(context, args);
                timeout = null;
            }, wait);
        }
    }
}

③结合时间戳和定时器

function throttle(func, wait) {

    var timer = null;
    var startTime = Date.now();  

    return function(){
        var curTime = Date.now();
        var remaining = wait-(curTime-startTime); 
        var context = this;
        var args = arguments;

        clearTimeout(timer);

        if(remaining<=0){ 
            func.apply(context, args);

            startTime = Date.now();

        }else{
            timer = setTimeout(fun, remaining);  // 如果小于wait 保证在差值时间后执行
        }
    }
}

六、节流防抖总结

防抖:原理是维护一个定时器,将多次相同操作合并为一次。规定在 delay 后触发函数,若在此之前再次触发,则取消之前的计时并重新计时,只有最后一次操作会被触发。

节流:原理是判断是否达到特定时间间隔以触发事件。在某个时间段内,函数只能触发一次。

区别:防抖仅在最后一次事件后执行触发函数;而节流无论事件多么频繁,都保证在规定时间段内触发一次事件函数。

到此这篇关于JavaScript深入理解节流与防抖的文章就介绍到这了,更多相关JS 节流与防抖内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章