likes
comments
collection
share

手写防抖节流,这次还搞不懂我跟你姓

作者站长头像
站长
· 阅读数 15

LOL和王者大家都玩过吧?我们今天就拿这个来举例

什么是防抖?

从字面意思来理解,防抖就是防止抖动,避免事件的重复触发。 LOL(王者)里按下了回城键,那么在8秒钟之后,就会执行回城事件,如果此时你再次回城,那回城时间将会重置,又需要重新过8秒才会回城。 还有就是输入框的输入,网页的滚动条,不可能是实时响应的,都是做了防抖处理的,只执行最后一次

所以,在一段时间内,将多次的高频操作做到触发最后一次 就是防抖

什么是节流?

节流就更好理解了,英雄技能就是一个典型的节流,每释放一次技能之后都需要冷却时间。这个技能是必须等cd转完之后才能再次释放, 所以说,规定的时间里只执行一次就是节流

手写防抖
// html
<input type="text">

// js 
const dom = document.querySelector('input');

dom.oninput = myDebounce(function () {
// 这儿为什么不是 箭头函数   箭头函数的话  这儿就是window了  匿名函数的话this就指向fn的调用者==> dom
    console.log('dom事件里面的this', this);
    console.log(dom.value + '看我',);
}, 300);

function myDebounce(fn, delay) {
    console.log('防抖顶层函数的 this', this); // 这儿是window
    let timer = null;
    return function () {
    //这儿为什么不是 箭头函数 ?同理 箭头函数的话  这儿this就是window了  匿名函数的话this就指向fn的调用者==> dom
        console.log('return 函数 的 this', this);
        if (timer) {
            clearTimeout(timer)
        };
        // 这儿箭头函数就是为了绑定 this 不被改变  这儿用 function this指向会改变的 (setTimeout中非箭头函数调用者为window,但原 fn 调用者可能不是window,所有需要保持原来的this指向)
        timer = setTimeout(() => {
            // 闭包来封装定时器变量,这样做可以保护定时器变量不被非法更改
            console.log('定时器的 this', this);
            // fn 函数就这样调用的话  this就是 window ,也没有事件源对象(严格模式下this指向 undefind)  所以直接调用 fn 是不行的。
            // apply使 this 指向调用者,为了保证设置防抖之后响应事件内部的this指向和事件源对象  与未设置防抖之前保持一致
            // arguments获取传入的参数(是一个伪数组,arguments指当前return这个函数接收的参数,该参数原本就是需要传给 fn 的 ) 。
            fn.apply(this, arguments)
            timer = null
        }, delay);
    }
};

原理其实很简单:

  • 需要一个函数来接收要处理的函数fn)和节流的时间(delay)
  • 返回一个处理之后(设置了节流)的函数
    • 用一个定时器来实现延迟多少时间 且 执行传进来的函数(fn)的功能
    • 注意 this 指向 和 接受参数 以及 清除定时器

说到接收参数,很多小伙伴可能入使用的 rest 比较多 也可以这样写

function myDebounce(fn, delay) {
    let timer = null;
    return function (...args) {
        if (timer) {
            clearTimeout(timer);
        };
        timer = setTimeout(() => {
            fn.apply(this, args);
            timer = null;
        }, delay);
    }
};
dom.oninput = myDebounce(function (event) {
    console.log(event);
}, 500);
手写节流
function myThrottle(fn, delay) {
    let timer = null;
    return function () {
        // 这儿是重点  如果时间没到  那么 timer 就不为 null  就会返回  不会往下执行!!!
        if (timer) {
            return
        };
        timer = setTimeout(() => {
            // 时间到了  timer  被设置为null  就走到这儿来了 就会执行 
            fn.apply(this, arguments)
            timer = null
        }, delay);
    }
};

// 综合上面两步 就实现了 英雄 技能cd  一样的节流

一法通,万法通,万变不离其宗,只要掌握了核心科技,可以玩出花

时间戳版本
function myThrottle(fn, waitTime) {
    var lastTime = 0;// 上一次的时间
    return function () {
        var nowTime = new Date().getTime(); // 获取当前时间戳
        if (nowTime - lastTime > waitTime) {
            // 当前时间减去上一次的时间,如果大于 waitTime(你设置的时间) 就可以再次操作了
            fn.apply(this, arguments); 
            // 将当前时间复制给上一次的时间
            lastTime = nowTime;
        }
    }
};

时间戳版本也是为了实现 一个时间间隔 也就是 上面所说的 规定的时间里只执行一次 因为每次执行完成之后上一次的时间 lastTime 都是刚刚的当前时间 nowTimenowTime - lastTime > waitTime 都明白吧?其实就是这个 ==> nowTime > lastTime + waitTime 意思就是 必须等 xx 秒 时间之后 你这个等式才能成立 然后才能执行