[陈同学 i 前端] 手写经典 | 防抖与节流

134 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

前言

大家好,我是陈同学,一枚野生前端开发者,感谢各位的点赞、收藏、评论

身为一名技术人员,面对常见的编程场景立刻给出相应的解决方案是拥有良好能力的体现,本节我们来复习一下防抖节流的手写

本文阅读成本与收益如下:

阅读耗时:3mins

全文字数:4k+

预期效益

  • 掌握防抖、节流的实现

常见背景

假设现在我们手头上有一个项目

项目当中有两个需求:

  1. 存在一个搜索框,搜索框要支持用户输入完停止1秒后进行远程自动搜索

  2. 存在一个按钮,用户会在短时间内连续点击多次,但每次点击都会发起一个请求

这两个需求都有可能造成请求瀑布的出现

那么要解决这个问题,我们就需要实现防抖节流的能力

防抖

防抖:在一定时间内重复触发条件,取消定时器再新建定时器,保证触发后经过一定时间才激活回调函数

function debounce(fn, wait = 1000) {
    // 保存定时器引用
    let timeout;
    return function() {
        let args = arguments;
        let context = this;
        // 清除定时器
        clearTimeout(timeout);
        // 新增定时器
        timeout = setTimeout(function() {
            // 定时器倒计时结束,激活回调函数
            fn.apply(context, args);
        }, wait);
    }
}
let resFn = debounce(() => {}, 1000);
resFn();
resFn();

如上debounce函数返回了一个含有timeout引用闭包的匿名函数,该匿名函数在真实业务中会被多次调用,如搜索框内容变动时便会触发该匿名函数

而目前实现还缺少我们日常使用中会用到的两个能力:取消立即执行

function debounce_full(fn, wait = 1000, immediate = false) {
    let timeout, result;
    const debounced = function() {
        let context = this;
        let args = arguments;
        if(timeout) {
            clearTimeout(timeout);
        }
        // 支持立即执行
        if(immediate) {
            const callNow = !timeout;
            timeout = setTimeout(function() {
                timeout = null;
            }, wait);
            if(callNow) {
                result = fn.apply(context, args);
            }
        }else{
            timeout = setTimeout(function() {
                fn.apply(context, args);
            }, wait);
        }
        return result;
    }
    // 支持取消定时器
    debounced.cancel = function() {
        clearTimeout(timeout);
        timeout = null;
    }
    return debounced;
}
let resFn = debounce_full(() => {}, 1000, true);
resFn();
resFn();
resFn.cancel()

加上这两个功能能够正常使用啦!

节流

防抖:在一定时间内重复触发条件,不做任何操作,首次触发后经过一定时间才激活回调函数

function throttle(fn, wait) {
    let previous = 0;
    return function() {
        let context = this;
        let args = arguments;
        let now = +new Date();
        if(now - previous > wait) {
            fn.apply(context, args);
            previous = now;
        }
    }
}
let resFn = throttle(() => {
    console.log('hello');
}, 1000);
resFn();
resFn();
resFn();

如上实现了一个简易版本的节流,此处只会打印hello一次,即第一次运行resFn

后面两次执行由于均在第一次执行后1000ms以内,不会被执行

当然了,实现当中也可以添加选项入参用于控制回调函数的执行时机

function throttle_full(func, wait, options = {}) {
    let timeout, context, args, result;
    let previous = 0;
    let later = function() {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if(!timeout) {
            context = args = null;
        }
    };
    let throttled = function() {
        let now = +new Date();
        if(!previous && options.leading === false) {
            previous = now;
        }
        let remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if(remaining <= 0 || remaining > wait) {
            if(timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
            if(!timeout) {
                context = args = null;
            }
        }else if(!timeout && options.trailing !==false) {
            timeout = setTimeout(later, remaining);
        }
    }
    throttled.cancel = function() {
        clearTimeout(timeout);
        previous = 0;
        timeout = null;
    }
    return throttled;
}

讲到最后

防抖和节流在日常工作或面试当中都会经常出现,只需要好好掌握一次!

本节代码实现较为粗糙,同学们可以自行完善

感谢各位看到这里,如果你觉得本节内容还不错的话,欢迎各位的点赞、收藏、评论,大家的支持是我做内容的最大动力

本文为作者原创,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利