节流和防抖的手写实现

防抖

代码实现
假设情况 假设这里我要实现的一个场景是 用于多次点击登录按钮 那么久会多次频繁的发送网络请求给后端 我们希望在用户多次频率特别高的点击中 只会触发最后一次 这时候 就用到了节流

let btn = document.querySelector('.btn');
function debounce(fn, delay){
    let timeout = null;
    return function(){
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn()
        }, delay);
    }
}
function show(){
    console.log(1);
}
btn.addEventListener('click', debounce(show, 2000));

在这里 实现了一个简易的节流函数 可以看到 是使用了闭包的 这里debounce函数是直接被调用的 返回了一个匿名函数 从而有了闭包的存在 当我第一次点击的时候 这时候timeout是空的 所以clearTimeout没有清除任何定时器 当我第二次点击的时候 此时已经有定时器了 那么之前的第一个定时器就会被清除 重新去设置了一个定时器 如果用户频繁的点击的话(频率在2000毫秒内) 就会每次都去清除之前的定时器 然后重新设置一个定时器 直到用户停止点击(或是在2000毫秒之后点击) 那么这个fn函数才会被触发 也就是在用户停止点击2000毫秒后这个fn函数才会被触发 我们可以在这里去发起网络请求

存在的问题

1 this指向问题
实际上在btn.addEventListener()这个监听中 第二个参数是一个函数 会在触发比如这里是click事件的时候去执行这个函数 函数的this的指向就是这个元素

btn.addEventListener('click', function(){
    console.log(this);
    // <button class="btn">按钮</button>
})

但是在 fn函数中this 的指向是window

function show(){
    console.log(this)
    // window
    console.log(1);
}

解决办法 改变fn函数中this的指向问题

let btn = document.querySelector('.btn');
function debounce(fn, delay){
    let timeout = null;
    // 返回的这个函数的this指向就是 btn这个按钮 因为这是事件绑定的机制 可以自己去看看 后面我也会去记录下这方面的知识
    return function(){
        clearTimeout(timeout);
        timeout = setTimeout(() => {
        // 在这里 我直接绑定的this 是因为我使用的是箭头函数的方式 所以箭头函数的this就是它别定义的上下文 可以看我之间的那篇文章
            fn.apply(this)
        }, delay);

    }
}
function show(){
    console.log(1);
}
btn.addEventListener('click', debounce(show, 2000));

/*
如果不理解 也可以使用这种方式 
function debounce(fn, delay){
    let timeout = null;
    // 返回的这个函数的this指向就是 btn这个按钮 因为这是事件绑定的机制 可以自己去看看 后面我也会去记录下这方面的知识
    return function(){
        const that = this;
        clearTimeout(timeout);
        timeout = setTimeout(function(){
        // 这里 没有使用箭头函数的方式 所以这里我要讲返回的那个函数的this给记录下来 然后在这里去进行一个绑定
            fn.apply(that);
        }, delay)

    }
}
*/

2 事件对象event的问题
实际上在btn.addEventListener()这个监听中 第二个参数是一个函数 会在触发比如这里是click事件的时候去执行这个函数 函数中会有一个默认的参数event事件对象

btn.addEventListener('click', function(e){
    console.log(e)
    console.log(this);
    // <button class="btn">按钮</button>
})

大家可以自行的打印一下 是一个event对象 里面还包含挺多东西的 但是在我们要当点击后哟啊执行的fn函数中 是没有这个event事件对象的 打印出来的是 undefined

function show(e){
    console.log(this);
    // window
    console.log(e);
    // undefined
    console.log(1);
}

解决办法 将 event 对象传入fn函数中

let btn = document.querySelector('.btn');
function debounce(fn, delay){
    let timeout = null;
    return function(){
    // 获取这个返回函数的参数 也就是event对象
    let args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    }
}
function show(){
    console.log(1);
}
btn.addEventListener('click', debounce(show, 2000));

代码实现-增加一个功能

在这里 我们是点击了过了delay毫秒之后再去执行这个函数 那么现在我想的是 我不仅可以实现过了delay毫秒后去执行 还可以实现的一个功能是点击后立即执行 最后一次点击不会执行 你想用哪种方式都可以 解决思路 我们可以往节流函数 debounce函数中再去传入一个参数bool值 让你自己传入true/fasle 去选择你要的一个模式

let btn = document.querySelector('.btn');
function debounce(fn, delay, bool){
    let timeout = null;
    return function(){
        const args = arguments;
        clearTimeout(timeout);
        if(bool) {
            if(!timeout) {
                fn.apply(this, args);
            }
            timeout = setTimeout(() => { timeout = null}, delay);

        }else {
            timeout = setTimeout(() => {
                fn.apply(this, args);
            }, delay);
        }

    }
}
function show(){
    console.log(1);
}
btn.addEventListener('click', debounce(show, 2000, true));

总结

两种模式的切换 一个是点击后当有频繁点击的时候 会在最后一次点击后 过了 delay毫秒之后执行 一个是点击后立即执行后面你再频繁点击后 都不会执行 直到最后一次频繁点击后 过了delay毫秒后再次点击 才会又重复这个过程 可以看代码自己好好体会下

节流

代码实现
这里就不写上面那些存在的问题了 其实都是一样的问题 上面已经解决了 这里就直接写代码的实现了

代码实现-1 时间戳的方式
function throttle(fn, delay, bool){
    let  previous = 0;
    return function(){
        let now = new Date().valueOf();
        if(now - previous > delay){
            fn.apply(this, arguments);
            previous = now;
        }
    }
}

这里 当用户频繁点击的时候 只会在规定的delay时间内触发一次 过了delay时候 再触发 一直重复

代码实现2- 定时器的方式
function throttle(fn, delay, bool){
    let timeout = null;
    return function(){
        let args = arguments;
        if(!timeout){
            timeout = setTimeout(() => {
                timeout = null;
                fn.apply(this, args);
            }, delay)
        }
    }
}

这里 是当用户频繁点击后 会在规定的delay时间里 最后一次会触发

节流和防抖函数的区别

当用户频繁点击的时候 防抖函数是会在用户的最后一次点击后的delay时间后触发 而节流函数是会无论用户怎么频繁点击 都会在规定的delay时间内去触发一次

装载于:https://juejin.cn/post/6974670402727444494

扒皮猴社区版权所有,转载请标注出处!
扒皮猴 » 节流和防抖的手写实现

发表评论