Skip to content

函数防抖和节流 #132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
funnycoderstar opened this issue Apr 19, 2020 · 1 comment
Open

函数防抖和节流 #132

funnycoderstar opened this issue Apr 19, 2020 · 1 comment

Comments

@funnycoderstar
Copy link
Owner

funnycoderstar commented Apr 19, 2020

要实现一个xxx函数,首先要明白这个函数的意思,然后思考一些,需要接受的参数,以及需要返回什么

函数防抖(debounce)

防抖:不管事件触发频率多高,一定在事件触发 n 秒后才执行,如果在一个事件执行的 n秒内又触发了这个事件,就以新的事件的时间为准,n秒后才执行,总之,触发完事件 n 秒内不再触发事件,n秒后再执行。

思路:

  1. 返回一个函数;
  2. 每次触发事件时都取消之前的定时器

需要注意问题:

  1. this指向
  2. 参数的传递
  3. 是否要立即调用一次
function debounce(fn, wait, immediate) {
    let timer = null;
    //  返回一个函数
    return function(...args) {
        // 每次触发事件时都取消之前的定时器
        clearTimeout(timer);
        // 判断是否要立即执行一次
        if(immediate && !timer) {
            fn.apply(this, args);
        }
        // setTimeout中使用箭头函数,就是让 this指向 返回的该闭包函数,而不是 debounce函数的调用者
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, wait)
    }
}

通过闭包保存一个标记(timer)来保存setTimeout返回的值, 每当要触发函数的时候, 需要先把上一个setTimeout清除掉, 然后再创建一个新的setTimeout, 这样就能保证执行函数后的 wait 间隔内如果还要触发函数, 就不会执行fn

使用场景

  1. 监听resize或scroll,执行一些业务处理逻辑
window.addEventListener('resize', debounce(handleResize, 200));
window.addEventListener('scroll', debounce(handleScroll, 200));

window 的 resize、scroll, mousedown、mousemove, keyup、keydown等高频触发的事件

  1. 搜索输入框,在输入后200毫秒搜索
debounce(fetchSearchData, 200);

可以这样去理解记忆:函数防抖是 在事件触发 n 秒后才执行,在监听 scroll事件和 resize 事件时,只要 n 秒后才执行一次就可以了,不需要每次只要一触发 scrollresize的时候就执行,n秒内的执行是没有意义的(用户可能都感受不到,而且很容易造成卡顿)。

函数节流(throttle)

函数节流:不管事件触发频率有多高,只在单位时间内执行一次。
简单分析一下我们要实现的函数的一些信息

  • 接收参数:
    • fn:要节流的函数
    • wait: 等待的时间
  • 返回值:一个新的节流函数

有两种思路实现: 使用时间戳和定时器

使用时间戳

function throttle(fn, wait)  {
    // 记录上一次执行的时间戳
    let previous = 0;
    return function(...args) {
        // 当前的时间戳,然后减去之前的时间戳,大于设置的时间间隔,就执行函数,否则不执行
        if(Date.now() - previous > wait) {
            // 更新上一次的时间戳为当前时间戳
            previous = Date.now();
            fn.apply(this, args);
        }
    }
}

第一次事件肯定触发,最后一次不会触发(比如说监听 onmousemove,则鼠标停止移动时,立即停止触发事件)

使用定时器

function throttle(fn, wait)  {
    // 设置一个定时器
    let timer = null;
    return function(...args) {
        // 判断如果定时器不存在就执行,存在则不执行
        if(!timer) {
            // 设置下一个定时器
            timer = setTimeout(() => {
                // 然后执行函数,清空定时器
                timer = null;
                fn.apply(this, args)
            }, wait)
        }
    }
}

第一次事件不会触发(fn是放在 setTimeout中执行的,所以第一次触发事件至少等待 wait 毫秒之后才执行),最后一次一定触发

定时器和时间戳结合

两者结合可以实现,第一次事件会触发,最后一次事件也会触发

function throttle(fn, wait)  {
    // 记录上一次执行的时间戳
    let previous = 0;
    // 设置一个定时器
    let timer = null;
    return function(...args) {
        // 当前的时间戳,然后减去之前的时间戳,大于设置的时间间隔
        if(Date.now() - previous > wait) {
            clearTimeout(timer);
            timer = null
            // 更新上一次的时间戳为当前时间戳
            previous = Date.now();
            fn.apply(this, args);
        } else if(!timer) {
            // 设置下一个定时器
            timer = setTimeout(() => {
                timer = null;
                fn.apply(this, args)
            }, wait)
        }
    }
}

比较节流和防抖的可视化界面

参考

@konglingwen94
Copy link

以前实现函数节流的方法时没有考虑过回调函数首次、末尾是否执行的情况,这次受用了,支持一下!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants