前言
上一期我们介绍了闭包的相关知识,知道了闭包具有保存外部变量的特性,同时深入理解了它的弊端————消耗内存,需手动干掉,还利用闭包,手写了一个函数柯里化。那么这一期,我们将带来两个与闭包紧密联系的概念——————防抖&节流。
防抖&节流是用来干嘛的?
防抖(Debounce)和节流(Throttle)都是前端开发中常用的性能优化技术,它们的主要作用是控制函数的执行频率,从而减少不必要的计算和资源消耗,提高应用的性能和用户体验。
让我们从滚动条监听开始说起:
滚动事件
window.onscroll = scrollHandle
function scrollHandle(){
var scrollTop = document.documentElement.scrollTop;
console.log('滚动条位置: '+scrollTop);
}
在运行的时候我们会发现一个问题:这个函数默认执行的频率太高了。高到什么程度?以chrome浏览器为例,按一次【向下方向键】,会发现函数执行了8-9次。
实际上我们并不需要如此高频的反馈,毕竟浏览器的性能有限,我们要考虑如何尽可能的去优化这个场景,由此,防抖和节流应运而生。
防抖(Debounce)
防抖的概念&基本思想
防抖(Debounce)是一种常见的前端性能优化技术,用于限制函数的调用频率。它通过延迟函数的执行来确保在短时间内多次触发的情况下,函数只会在最后一次触发后的某个时间间隔内执行一次。这样可以有效减少不必要的计算和资源消耗,提高应用的响应性和性能。
防抖的基本思想是:当一个事件频繁触发时,设置一个定时器,如果在设定的时间间隔内再次触发该事件,则取消之前的定时器并重新设置一个新的定时器。只有在最后一次触发事件后的一段时间内没有新的触发时,才会真正执行函数。
简而言之就是:在一定的时间内,只会执行最后一次,其次的都会被干掉!
阮一峰老师有一个经典的比喻来解释防抖:想象你在电梯里,电梯门不会因为你按了一次按钮就立即关闭,而是会等待一段时间,看是否还有人要进入电梯。如果有新的乘客进来,计时器就会重置,直到一段时间内没有人再进来,电梯门才会关闭。
手写防抖!拿下面试官!
就拿上面的例子来说吧,假如有个用户就是闲的没事,喜欢把滚动条拖来拖去,拖来拖去~导致scrollHandle函数不断执行,导致浏览器性能不好,那么我们该如何利用防抖解决呢?
记住,防抖的思想是:在一定的时间内,只会执行最后一次,其次的都会被干掉!
本着这个思想,我们来设计防抖的函数:
function debounce(fn,delay){
// 传入要防抖的函数,决定延迟的时间
}
我们只要执行最后一次触发的计时器函数,那么之前的就都得干掉呗,如何找到之前的定时器呢?
这就要提到定时器的一个特性————————定时器执行通常会返回一个唯一的标识符(通常为数字)
这样,我们可以用一个变量装载它,当这个变量有值就说明前面的定时器存在,我们利用clearTimeout来消除计时器即可:
function debounce(fn,delay){
return function(args){
if(fn.id){
clearTimeout(fn.id)
}
}
}
如果没有定时器,我们就可以添加一个定时器,令函数经过delay毫秒后执行:
function debounce(fn,delay){
return function(args){
if(fn.id){
clearTimeout(fn.id)
}
fn.id = setTimeout(()=>{
fn(args);
},delay)
}
}
这样一个防抖函数就完成了,结合上面滑动滚动条的函数,现在先滚一下~再在1s内暂停滚动后,才会执行函数。
function scrollHandle(){
var scrollTop = document.documentElement.scrollTop;
console.log('滚动条位置: '+scrollTop);
}
function debounce(fn,delay){
return function(args){
if(fn.id){
clearTimeout(fn.id)
}
fn.id = setTimeout(()=>{
fn(args);
},delay)
}
}
window.onscroll = debounce(scrollHandle,1000);
实际应用场景
1. 搜索框输入建议
当用户在搜索框中输入内容时,我们不希望每次按键都发送请求获取建议,而是希望在用户停止输入一段时间后再发送请求。这样可以减少不必要的网络请求,提高用户体验。
最好的体现就是在搜索引擎中,如果不用防抖或者节流,那么当我们每输入一个字符,它就要在数据库的亿条结果遍历一遍,当我们输入下一字符时,它的遍历还没结束就又重新开始搜索了,这就把它累死了233333~
所以我们需要防抖/节流来调控一下。
const searchInput = document.getElementById('search-input');
const getSearchSuggestions = debounce(() => {
// 发送请求获取搜索建议
console.log('Fetching search suggestions...');
}, 300);
searchInput.addEventListener('input', getSearchSuggestions);
2. 窗口调整大小
当窗口大小发生变化时,我们不希望每次变化都重新计算布局或重绘,而是希望在用户停止调整窗口大小一段时间后再进行处理。
const handleResize = debounce(() => {
// 重新计算布局或重绘
console.log('Handling resize event...');
}, 300);
window.addEventListener('resize', handleResize);
节流(Throttle)
防抖固然很好,但是呢,他还是有些缺陷,不符合我们大部分的预期。
如果使用防抖来处理问题的结果是:
在限定时间内,不断触发滚动事件(比如某个用户闲得无聊,按住滚动不断拖来拖去),只要不停止触发,理论上就永远不会输出当前到顶部的距离
但如果产品同学期望的处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔中给出反馈呢?
这样就可以设计出一种像阀门一样定期开放的函数,让函数在执行一段时间后短暂失效,过了这段时间后再次重新激活(类似于技能冷却时间)
效果:如果短时间内触发大量同一事件,那么在函数执行一次之后,该函数在指定的期限内不再工作,直到过了这段时间才重新生效。
节流的概念&思想
节流(Throttle)是一种前端性能优化技术,用于限制函数的执行频率。它确保在一定时间间隔内,无论事件被触发多少次,函数只执行一次。 样可以有效减少不必要的计算和资源消耗,提高应用的响应性和性能。
节流的基本思想是:当一个事件频繁触发时,在设定的时间间隔内,只允许该事件触发一次。 使在这段时间内事件被多次触发,也只会执行一次函数。这种机制适用于需要定期执行的任务,而不是每次触发都执行。
手写节流!征服面试官!
如何手写一个节流呢?实际上本质都是一样的,我们只需要记住在设定的时间间隔内,只允许该事件触发一次。 这个核心思想就好了。
首先老样子,我们传入需要的函数和参数:
function throttle(fn,delay){
return function(...args){
}
}
}
既然我们需要让它在设定的时间间隔内只触发一次,也就是说我们需要一个计时器来告诉引擎什么时候可以执行,什么时候不能执行呗:
function throttle(fn,delay){
let state = true; // true代表可以执行
return function(...args){
if(state === true){
fn(...args); // 此时可以执行函数
}
}
}
这个时候是可以执行的,那么接下来就是设置delay毫秒的锁,把这个锁住,不让它执行:
function throttle(fn,delay){
let state = true; // true代表可以执行
return function(...args){
if(state === true){
fn(...args);
state = false; // 执行完之后直接令state = false,使它不可以执行函数
setTimeout(()=>{
state = true; // delay 毫秒后,设置为true,此时又可以执行函数
},delay)
}
}
}
这样就成功利用state设置了一个delay ms的锁.
对这个函数优化一下,我们绑定一下this:
function throttle(fn,delay){
let state = true; // true代表可以执行
return function(...args){
var context = this;
if(state === true){
fn.apply(context,...args);
state = false; // 执行完之后直接令state = false,使它不可以执行函数
setTimeout(()=>{
state = true; // delay 毫秒后,设置为true,此时又可以执行函数
},delay)
}
}
}
当然计时也可以使用时间戳:
function throttle(fn, delay) {
let last,// 上一次的执行时间
deferTimer; // timeout id
return function (...args) {
let that = this; // 闭包的应用场景
let now = + new Date(); // 类型转换
// let args = arguments;
if (last && now < last + delay) {
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(that, args);
}, delay)
}
else {
last = now;
fn.apply(that, args);
}
}
}
+为加法运算符,但也可以将Date转化为毫秒的时间戳,由此来实现计时。
对于上面滑动滚动条的应用结合:
window.onscroll = throttle(scrollHandle,500)
function scrollHandle(){
var scrollTop = document.documentElement.scrollTop;
console.log('滚动条位置: '+scrollTop);
}
function throttle(fn,delay){
let state = true;
return function(...args){
var context = this;
if(state === true){
fn.apply(context,...args);
state = false;
setTimeout(()=>{
state = true;
},delay)
}
}
}
效果如下:
实际应用场景
1. 滚动事件处理
当用户滚动页面时,我们不希望每次滚动都执行某些操作(如加载更多数据),而是在固定的时间间隔内只执行一次。
const handleScroll = throttle(() => {
// 执行滚动处理逻辑
console.log('Handling scroll event...');
}, 200);
window.addEventListener('scroll', handleScroll);
function throttle(func, limit) {
let inThrottle;
return function(...args) {
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
2. 鼠标移动事件
当用户拖动元素时,我们不希望每次鼠标移动都更新元素的位置,而是在固定的时间间隔内只更新一次。
const handleMouseMove = throttle((event) => {
// 更新元素位置
console.log(`Mouse moved to: ${event.clientX}, ${event.clientY}`);
}, 100);
document.addEventListener('mousemove', handleMouseMove);
function throttle(func, limit) {
let inThrottle;
return function(...args) {
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
3. 键盘事件处理
当用户快速按键时,我们不希望每次按键都执行某些操作,而是在固定的时间间隔内只执行一次。
const handleKeyPress = throttle((event) => {
// 处理按键事件
console.log(`Key pressed: ${event.key}`);
}, 100);
document.addEventListener('keydown', handleKeyPress);
function throttle(func, limit) {
let inThrottle;
return function(...args) {
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
总结
防抖和节流都是为了减少函数执行频率,优化性能而生,但它们之间又有些区别:
在防抖中,函数只会在最后一次触发后的某个时间间隔内执行一次
节流则是:每隔一段时间间隔就可以触发一次函数
Plus:+ new Date() 可以将其转换为毫秒计数的时间戳形式
只要记住了这两点规则,就可以手写出防抖和节流啦!
这一期就到这里了,如果有错误,也希望大家指出呀!拜拜咯!