大前端开发

js 防抖和节流

2020-12-15  本文已影响0人  理发师Tony

防抖

防抖是js优化的重要的一部分,也是面试中手写代码最常考的题目。那么我们为什么要防抖?防抖是什么意思?
比如我们在监听onkeyup事件中,监听input中输入的文字,我们可以在console中可以看到input的文字打印。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input id="input1" type="text">
</body>
<script src="index.js"></script>
</html>
const input1 = document.getElementById('input1');
// console.log(input1);
input1.addEventListener('keyup', function() {
    console.log(input1.value);
})
console打印
非常简单。但是每打一个字就监听到了,就很耗性能。
有人就问了,诶?那我能不能等我打完一小部分字段了以后,然后浏览器才去监听呢?
又有人说了,用setTimeout不就完了,过个一秒再监听。
所以,防抖这个词就来了。

所以我们在刚才的index.js中的代码中,input1的监听中,加入一个定时器试试:

const input1 = document.getElementById('input1');
// console.log(input1);
let timer = null;
input1.addEventListener('keyup', function() {
    if(timer) {
        clearTimeout(timer);
    }
    timer = setTimeout(() => {
        console.log(input1.value);
        // 清空定时器
        timer = null;
    }, 500)
})

先演示一下:


加入防抖功能

可以看到,当我直接依次输入123456的时候,停顿了一会,然后浏览器才监听到我输入了,并打印123456,接着我又输入了789停止,半秒后,又打印了123456789,很妙。就是说,你一直输一直输,只要你中间输入的间断不超过500毫秒(咱们自己定的时间),它就不会出来,就一直等着你输,什么时候你输入的间断,暂停的或者停止的时间超过500毫秒,它才会把最终的结果打印出来。

用很通俗的话说一下这里面的原理:
①先弄个定时器,监听到keyup的时候看一下现在定时器存在吗?不存在,是null,那我们不管,往下走。
②接下来给定时器赋值setTimeout的异步任务,这个异步任务是500毫秒之后执行,执行的结果就是打印当前的input中的value。打印完之后,要清空定时器。

流程是酱的:
①输入个1,停止:
timernullif语句不触发,往下进行,500毫秒后输出value,然后清空timer,这是第一次,而且只输入一个1的情况下,比较简单。
②输入123,停止:
timernullif语句不触发,往下进行,500毫秒之后执行,timer有值了,这个时候,我们立马就输入2了,keyup又监听了,这时候500毫秒还没有到,监听之后,timer是有值的,if条件判断为true,因为之前输入1的时候,timer就有值了,所以我们把之前输入1的那个timer给清除掉,然后下面重新设置timer,500毫秒之后执行,但是还没开始执行的时候,又输入3了,立马又会出现keyup事件,进入if,有timer,这个timer是输入2的时候定义的,然后又清空了,重新设置了定时任务,500毫秒只有执行,这时候已经没有值进来了,所以500毫秒到了以后,输出刚才的123。接下来如果再输入一个4,是和第一次输入一个1是一样的。

写的比较啰嗦,但是这就是传说中的防抖

但是有个问题,就是我们为了一个input1,就写了这么多行代码,感觉好麻烦啊,如果我们又10个input,我们是不是得写10个这样的监听函数?
所以新的问题就来了,我们需要改进一下我们的防抖函数,将它简单的封装一下:

我们需要将防抖函数封装成一个公共方法,那么这个方法的参数应该有个函数fn,和一个delay时间延迟,然后最终要返回一个函数。
代码如下:

// 防抖
function debounce(fn, delay = 500) {
    // timer 是在闭包中的
    let timer = null; 
    return function() {
        if(timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments);
            // 清空定时器
            timer = null;
        }, delay)
    }
}
input1.addEventListener('keyup', debounce(function() {
    console.log(input1.value);
}), 600);

节流

但我们拖拽一个元素时,要随时拿到该元素被拖拽的位置,直接用drag事件,则会频发触发,很容易导致卡顿。节流的意思是:无论拖拽的速度有多快,都会每隔100ms触发一次。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #div1 {
            border: 1px solid #ccc;
            width: 200px;
            height: 100px;
        }
    </style>
</head>
<body>
    <div id="div1" draggable="true">可拖拽</div>
</body>
<script src="index.js"></script>
</html>
const div1 = document.getElementById('div1');
// console.log(div1);
div1.addEventListener('drag', function(e) {
    console.log(e.offsetX, e.offsetY);
})
拖拽打印
可以看到,drag基本上随时都在触发,这时候我们如果再调用别的js的功能,很容易造成负载验证,卡死。
所以我们和防抖一样,加个setTimeout
const div1 = document.getElementById('div1');
// console.log(div1);
let timer = null;
div1.addEventListener('drag', function(e) {
    if(timer) {
        return;
    }
    timer = setTimeout(() => {
        console.log(e.offsetX, e.offsetY);
        // 清空定时器
        timer = null;
    }, 100)
})

只不过这次如果不存在timer,我们直接return出去,就不管了,自生自灭,不响应了。知道100毫秒之后,打印了,并且timer被赋值为null,然后才会有timer的下一次setTimeout...具体的流程就不说了,和防抖是一样的。

同样,我们需要将节流函数封装一下,工具化:

// 节流
function throttle(fn, delay = 100) {
    let timer = null;
    return function() {
        if(timer) {
            return;
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments);
            timer = null;
        }, delay)
    }
}

div1.addEventListener('drag', throttle(function(e) {
    console.log(e.offsetX, e.offsetY);
}), 200)
上一篇 下一篇

猜你喜欢

热点阅读