js面试(节流)

一、节流

在JavaScript中,节流(throttle)是一种常用的性能优化技术,用于限制某个函数在一定时间内的执行频率。具体来说,节流函数允许你在一段时间内只执行一次回调函数,即使在这段时间内触发了多次事件。这有助于防止因为频繁触发事件而导致的性能问题。

节流的实现原理是,在事件被触发后,一个定时器会被设置。如果在定时器完成之前,相同的事件再次被触发,那么这次触发不做任何处理,直到定时器任务完成后,清空定时器,才能对以后新的触发事件做出响应。这样,在一定的时间间隔内,只会对触发事件做一次处理。

应用场景:

  1. 滚动事件处理:在网页或应用中,滚动事件可能会非常频繁地触发,如果不加以控制,可能会导致性能问题。通过使用节流,可以限制滚动事件处理函数的执行频率,提高页面的响应速度和流畅度。
  2. 网络请求:对于需要连续发送请求的场景,如滚动加载数据或自动完成搜索,过多的请求不仅会增加服务器压力,还可能导致用户体验下降。通过节流,可以减少请求的次数,降低服务器压力,同时保证数据的及时加载。
  3. 动画效果:在动画过程中,如果过度频繁地刷新和渲染,可能会造成闪烁和卡顿现象。通过节流,可以控制动画的渲染频率,提高动画的流畅性和用户体验。

二、前置准备

  1. 准备一个html文件和一个throttle.js文件,throttle.js文件用来编写节流函数

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流</title>
    </head>
    <body>
    <div class="throttle">触发节流事件</div>
    <script src="./throttle.js"></script>
    </body>
    </html>
    // throttle.js
    const throttle = () => {};
  2. 给div绑定点击事件

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流</title>
    </head>
    <body>
    <div class="throttle">触发节流事件</div>
    <script src="./throttle.js"></script>
    <script>
    const clickEvent = function (e) {
    console.log("点击事件触发", e, this)
    }
    document.querySelector(".throttle").addEventListener("click", clickEvent)
    </script>
    </body>
    </html>

    image-20240317143334764

  3. 将clickEvent方法传递给throttle进行处理

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流</title>
    </head>
    <body>
    <div class="throttle">触发节流事件</div>
    <script src="./throttle.js"></script>
    <script>
    const clickEvent = function (e) {
    console.log("点击事件触发", e, this)
    console.log("执行时间", new Date().getSeconds())
    }
    const throttleClickEvent = throttle(clickEvent, 2000)
    document.querySelector(".throttle").addEventListener("click", throttleClickEvent)
    </script>
    </body>
    </html>
    // throttle.js
    const throttle = (fun, time) => {
    return fun;
    };

    在上面修改了一下clickEvent,用来打印时间。目前的效果和一开始没什么区别,下面就开始写节流函数

三、基础节流实现

  1. 第一次触发事件,设置一个定时器,只要这个定时器存在,就不再对触发事件做出响应。直到定时器任务完成,清空定时器

    // throttle.js
    const throttle = (fun, time) => {
    let timer;
    return function () {
    if (!timer) {
    timer = setTimeout(() => {
    fun();
    timer && clearTimeout(timer);
    timer = null;
    }, time);
    }
    };
    };

    image-20240317145108032

    现在频繁触发点击事件,但会间隔2秒触发一次事件处理函数。所以基本实现了节流

  2. 修改this指向

    // throttle.js
    const throttle = (fun, time) => {
    let timer;
    return function () {
    if (!timer) {
    timer = setTimeout(() => {
    fun.apply(this);
    timer && clearTimeout(timer);
    timer = null;
    }, time);
    }
    };
    };

    image-20240317145325447

  3. 获取参数

    // throttle.js
    const throttle = (fun, time) => {
    let timer;
    return function (...args) {
    if (!timer) {
    timer = setTimeout(() => {
    fun.apply(this, args);
    timer && clearTimeout(timer);
    timer = null;
    }, time);
    }
    };
    };

    image-20240317145434824

    上面就实现了一个最基本的节流函数

四、立即执行

// throttle.js
const throttle = (fun, time, immediately = false) => {
let timer;
return function (...args) {
if (!timer) {
// 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
if (immediately) {
fun.apply(this, args);
timer = setTimeout(() => {
timer && clearTimeout(timer);
timer = null;
}, time);
} else {
timer = setTimeout(() => {
fun.apply(this, args);
timer && clearTimeout(timer);
timer = null;
}, time);
}
}
};
};

image-20240317150604902

五、尾部执行控制

  1. 首先我们修改一下html文件,方便测试

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流</title>
    </head>
    <body>
    <div class="throttle">触发节流事件</div>
    <input type="text" class="throttle-input">
    <script src="./throttle.js"></script>
    <script>
    const clickEvent = function (e) {
    console.log("点击事件触发", e, this)
    console.log("执行时间", new Date().getSeconds())
    }
    const inputEvent = function (e) {
    console.log("表单数据", e, this)
    console.log("执行时间", new Date().getSeconds())
    }
    const throttleClickEvent = throttle(clickEvent, 2000, true)
    document.querySelector(".throttle").addEventListener("click", throttleClickEvent)
    document.querySelector(".throttle-input").addEventListener("input", inputEvent)
    </script>
    </body>
    </html>

    image-20240317154851639

    目前没有做节流处理,每次输入都会触发

  2. 使用节流函数控制触发频率

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流</title>
    <style>
    input {
    width: 100%;
    }
    </style>
    </head>
    <body>
    <div class="throttle">触发节流事件</div>
    <input type="text" class="throttle-input">
    <script src="./throttle.js"></script>
    <script>
    const clickEvent = function (e) {
    console.log("点击事件触发", e, this)
    console.log("执行时间", new Date().getSeconds())
    }
    const inputEvent = function (e) {
    console.log("表单数据", this.value)
    console.log("执行时间", new Date().getSeconds())
    }
    const throttleClickEvent = throttle(clickEvent, 2000, true)
    const throttleInputEvent = throttle(inputEvent, 2000, true)
    document.querySelector(".throttle").addEventListener("click", throttleClickEvent)
    document.querySelector(".throttle-input").addEventListener("input", throttleInputEvent)
    </script>
    </body>
    </html>

    image-20240317155234084

    此时,第一次被触发了,然后两秒后,触发了第二次。但是最后一次触发事件却没有得到响应

  3. 修改节流函数

    // throttle.js
    const throttle = (fun, time, immediately = false, tail = false) => {
    let timer;
    let tailTimer;
    return function (...args) {
    if (!timer) {
    // 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
    if (immediately) {
    fun.apply(this, args);
    timer = setTimeout(() => {
    timer && clearTimeout(timer);
    timer = null;
    }, time);
    } else {
    timer = setTimeout(() => {
    fun.apply(this, args);
    timer && clearTimeout(timer);
    timer = null;
    }, time);
    }
    } else if (tail) {
    // 如果当前已经触发了响应事件,并且需要对最后一次触发做出回应,则将后面触发调用的处理函数加入定时器
    // 每一次有新的触发事件,则将上一次的定时器给清除,保证始终是最新的触发事件被加入
    tailTimer && clearTimeout(tailTimer);
    tailTimer = setTimeout(() => {
    fun.apply(this, args);
    }, time);
    }
    };
    };

    image-20240317155654229

    此时最后一次就会触发了。我们再来试一试不立即执行的情况

    const throttleInputEvent = throttle(inputEvent, 2000, false)

    image-20240317155933638

    通过控制台发现,最后一次输入的数字被打印了两次。为什么?

    image-20240317191622241

    首先,最后打印的肯定是放在tailTimer里面的方法,它在53秒执行,是在51秒的时候触发的。最后一次执行节流函数是52秒,它是在50秒的时候触发的。之所以这两个方法打印了同样的值,是因为最后一次触发点击事件是在51秒,两个定时器里面的方法,都是在51秒后执行的,等它们执行的时候,都会拿到最后那个最新的值。

    那立即执行为什么没有这个问题呢?因为立即执行打印的是当时获取到值。简单点说:就是延迟执行,在52秒和53秒都会打印51秒最后一次触发时候的值。立即执行的话,50秒的时候会最后一次使用timer,此时,直接打印出50秒时候的值,在接下来的2秒之内,timer不会再执行,在这2秒内,触发的最后一次事件会放进tailTimer里面。

    所以,对于延迟执行的情况,其实没有必要将最后一次加入到tailTimer,因为timer会获取到最后一次的值。

    // throttle.js
    const throttle = (fun, time, immediately = false, tail = true) => {
    let timer;
    let tailTimer;
    return function (...args) {
    if (!timer) {
    // 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
    if (immediately) {
    fun.apply(this, args);
    timer = setTimeout(() => {
    timer && clearTimeout(timer);
    timer = null;
    }, time);
    } else {
    timer = setTimeout(() => {
    fun.apply(this, args);
    timer && clearTimeout(timer);
    timer = null;
    }, time);
    }
    } else if (tail && immediately) {
    // console.log("尾部定时器当前记录参数:", args[0].srcElement.value);
    // 如果当前已经触发了响应事件,并且需要对最后一次触发做出回应,则将后面触发调用的处理函数加入定时器
    // 每一次有新的触发事件,则将上一次的定时器给清除,保证始终是最新的触发事件被加入
    tailTimer && clearTimeout(tailTimer);
    tailTimer = setTimeout(() => {
    fun.apply(this, args);
    }, time);
    }
    };
    };

    image-20240317192941966

    ​ 这下打印结果就没问题了。

    再来测试一下立即执行的情况

    const throttleInputEvent = throttle(inputEvent, 2000, true)

    image-20240317193141582

    立即执行的情况,就会通过tailTimer来打印最后的结果了。

六、完整代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流</title>
<style>
input {
width: 100%;
}
</style>
</head>
<body>
<div class="throttle">触发节流事件</div>
<input type="text" class="throttle-input">
<script src="./throttle.js"></script>
<script>
const clickEvent = function (e) {
console.log("点击事件触发", e, this)
console.log("执行时间", new Date().getSeconds())
}
const inputEvent = function (e) {
const value = this.value
console.log("表单数据", value)
console.log("执行时间", new Date().getSeconds())
}
const throttleClickEvent = throttle(clickEvent, 2000, true)
const throttleInputEvent = throttle(inputEvent, 2000, true)
document.querySelector(".throttle").addEventListener("click", throttleClickEvent)
document.querySelector(".throttle-input").addEventListener("input", throttleInputEvent)
</script>
</body>
</html>
// throttle.js
const throttle = (fun, time, immediately = false, tail = true) => {
let timer;
let tailTimer;
return function (...args) {
if (!timer) {
// 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
if (immediately) {
fun.apply(this, args);
timer = setTimeout(() => {
timer && clearTimeout(timer);
timer = null;
}, time);
} else {
timer = setTimeout(() => {
fun.apply(this, args);
timer && clearTimeout(timer);
timer = null;
}, time);
}
} else if (tail && immediately) {
// console.log("尾部定时器当前记录参数:", args[0].srcElement.value);
// 如果当前已经触发了响应事件,并且需要对最后一次触发做出回应,则将后面触发调用的处理函数加入定时器
// 每一次有新的触发事件,则将上一次的定时器给清除,保证始终是最新的触发事件被加入
tailTimer && clearTimeout(tailTimer);
tailTimer = setTimeout(() => {
fun.apply(this, args);
}, time);
}
};
};

更多的优化,比如:取消、获取返回值可以参考防抖的处理。

posted @   平平丶淡淡  阅读(282)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起