js手写系列 【debounce/throttle】(防抖/节流)

538 阅读1分钟

debounce/throttle

定义

  • 防抖(debounce):指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
  • 节流(throttle):指连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版

实现

防抖(debounce)

有这样一个需求,用户向输入框输入内容,当输入框内容改变就向服务器发送请求,我们可以这样来实现:

<h1>防抖</h1>
<input id="input">
// 获取input元素
const input = document.getElementById("input")
// 绑定输入事件
input.addEventListener("input", function (e) {
    console.log("发送请求"+ e.target.value);
})

如果不加防抖结果就是这样的:

防抖前.gif

可以发现,用户每输入一个字符都会发送一次请求,这样会对服务器造成一定压力,我们想让用户输入完之后,过几秒再去发送请求,这时候就可以用防抖函数来解决:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>debounce</title>
</head>
<body>
    <h1>防抖</h1>
    <input id="input">
    <script>
        // 防抖函数,接收两个参数,一个是要执行的函数,另一个是延迟时间
        function debounce(fn, delay) {
            // 定义一个空的变量
            let timer = null;
            // 返回一个函数
            return function(){
                // 判断timer是否为空
                if (timer) {
                    // 如果不为空,则清除定时器timer
                    clearTimeout(timer);
                }
                // 给timer赋值一个定时器
                timer = setTimeout(() => {
                    // 执行fn函数,改变函数的this指向,并传递参数
                    fn.apply(this, arguments)
                }, delay);
            }
        }

        // 获取input元素
        const input = document.getElementById("input")
        // 绑定输入事件
        input.addEventListener("input", debounce(function (e) {
            console.log("发送请求"+ e.target.value);
        }, 500))
    </script>
</body>
</html>

加上防抖函数之后,就变成了这样:

防抖.gif

节流(throttle)

我们又有另一个需求,当我们对一个盒子进行拖拽时,每拖拽到一个地方就把位置发送给服务器,我们可以这样实现:

<h1>节流</h1>
<div id="div" draggable="true"></div>
#div {
    height: 100px;
    width: 100px;
    background-color: orange;
}
// 获取input元素
const divDom = document.getElementById("div");
// 绑定拖拽事件
divDom.addEventListener("drag", function (e) {
    console.log("拖拽到" + e.offsetX + "," + e.offsetY);
})

不加节流函数结果是这样的:

节流前.gif

可以看到请求发送的是非常频繁的,这样对服务器的压力是很大的,因此我们需要加一个节流函数,下面是两种版本的节流函数:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>throttle</title>
    <style>
        #div {
            height: 100px;
            width: 100px;
            background-color: orange;
        }
    </style>
</head>
<body>
    <h1>节流</h1>
    <div id="div" draggable="true"></div>
    <script>
        // 定时器版节流函数,接收一个函数和一个延迟时间
        function throttle(fn, delay) {
            // 定义一个初始状态,为false
            let flag = false;
            // 返回一个函数
            return function () {
                // 如果flag为true,则直接返回,不再往下执行
                if(flag) return;
                // 修改flag为true
                flag = true
                // 设置定时器
                setTimeout(() => {
                    // 执行fn函数,改变函数的this指向,并传递参数
                    fn.apply(this, arguments);
                    // 修改flag为false
                    flag = false
                }, delay)

            }
        }

        // // 时间戳版节流函数,接收一个函数和一个延迟时间
        // function throttle(fn, delay) {
        //     // 初始时间为0
        //     let time = 0;
        //     return function () {
        //         // 获取当前时间戳
        //         let now = new Date().valueOf();
        //         // 判断当前时间减去上一时间是否大于延迟时间间隔
        //         if (now - time >= delay) {
        //             // 执行fn函数,改变函数的this指向,并传递参数
        //             fn.apply(this, arguments);
        //             // 让time等于当前时间
        //             time = now;
        //         }
        //     }
        // }

        // 获取input元素
        const divDom = document.getElementById("div");
        // 绑定拖拽事件
        divDom.addEventListener("drag", throttle(function (e) {
            console.log("拖拽到" + e.offsetX + "," + e.offsetY);
        }, 200))
    </script>
</body>
</html>

加上节流函数之后,结果就好多了:

节流.gif