防抖
前段时间发布了一篇关于手写防抖的文章,今天想来做一个提升,让各位全面理解防抖节流。
什么是防抖
我们写了一个简单页面,当鼠标经过容器,页面的count计数就会加一,并把这个count插入到页面中(innerHtml)
但是事实上,防抖是想把执行推到最后一次,把之前的触发全部取消,他的目的是为前端做性能的优化。在这个案例中,防抖就大可不必了,因为在这个案例中,事实上就是做了一个加1操作和dom更新,而这样的操作耗时是远远小于屏幕更新时间的。倘若我们的项目变得复杂了起来,是一些耗时的任务,当我们在这种场景下还不做防抖,不断的去触发,那么此时耗时任务所花销的时间就大于我们触发的时间,这样就无法的得到准确的执行机会,上一次的任务还没执行完,下一次的任务又被触发,这样就会造成页面的卡顿。因此我们必须减少任务执行的次数。
防抖的作用
- 提升性能:避免频繁触发一些高成本的操作,防止因过度频繁执行而导致系统资源浪费和性能下降。(将执行推到最后一次,只执行最后一次触发)
- 防止误操作:例如用户可能在短时间内多次重复操作,防抖可以确保只对最后一次稳定的操作进行响应,避免因瞬间多次操作带来的混乱。
- 优化用户体验:使交互更加流畅和自然,不会因为过于灵敏的触发而给用户带来困扰。
- 增强程序稳定性:减少不必要的频繁处理,降低可能因频繁操作引发错误的几率。
- 保证逻辑的准确性:确保只有在合适的时机执行相关操作,以符合预期的业务逻辑。
手写令面试官眼前一亮的防抖
防抖其实就是设置一个定时器,如果在定时器时间内,任务被频繁触发,则清除这个定时器并设置一个新的定时器,只有在规定时间内任务没有被再次触发,这个任务才会得到执行。基于此,我们来看看基础版本的防抖应该怎么写
基础版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#container {
width: 100%;
height: 200px;
line-height: 200px;
text-align: center;
color: #fff;
background-color: #444;
font-style: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var count = 1;
var container = document.getElementById('container');
function getUserAction() {
container.innerHTML = count++;
}
function debounce(fn, delay) {
// 闭包
var timer = null;
return function (event) {
// 事件的执行函数 this-> container
// 控制执行的次数
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args)
}, delay);
// fn this -> 普通函数来运行
}
}
container.onmousemove = debounce(getUserAction, 1000)
</script>
</body>
</html>
debounce防抖函数中返回了一个函数,在这里就形成了一个timer闭包,供返回函数使用,通过timer对定时器进行保留,通过对定时器时间的控制timer = setTimeout(fn,delay)和定时器的删除clearTimeout(timer),实现了防抖的效果。具体对基础版本的讲解,可以看手写防抖,你也应该学会。this?闭包? - 掘金 (juejin.cn)
在基础版中,我们对原来要操作的getUserAction函数包裹了一个debounce防抖函数,在这种情况下,this的指向就会丢失,本来是getUserAction函数的this绑定到container身上,现在由于debounce的包裹,变成了debounce的this绑定到container身上,因此在基础版中我们做了this的传递,通过context上下文变量保存this并通过apply方法做一个显示绑定,除此之外,基础版中还对arguments参数进行了传递,确保我们的原本需要执行的函数能够拿到这些参数。那么之所以称之为基础版,是因为他还有更加惊艳人的地方。
进阶版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写debounce</title>
</head>
<body>
<script>
// 业务层 防抖功能 为了性能优化 减少请求次数
// fn 是真正需要执行的函数 this args权力
// delay 防抖时间 定时器id clear 最后一次
// immediate 立即执行一次
function debounce(fn, delay, immediate) {
// 自由变量空间
var timer, result;
// 真正执行的函数
return function () {
var context = this;
var args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
var callNow = !timer;
timer = setTimeout(function () {
timer = null;
}, delay)
if (callNow) result = fn.apply(context, args);
} else {
timer = setTimeout(function () {
result = fn.apply(context, args);
}, delay)
}
return result;
}
}
function getUserAction() {
container.innerHTML = count++;
}
debounce(getUserAction, 1000, true)
</script>
</body>
</html>
在进阶版中,我们添加了一个imeediate参数,如果设置了 immediate 为 true 且当前没有定时器时,会立即执行一次函数,然后再设置定时器,在定时器到期后再次执行函数;如果 immediate 为 false ,则只在定时器到期后执行函数。除此之外,我们还添加了一个返回值result,在一些情况下,我们也许需要用到这个result,因此从开发的角度来看,我们有必要对结果进行保留,以便日后使用。
代码详细分析:
-
debounce函数接受要执行的函数fn、防抖时间间隔delay以及是否立即执行一次的标志immediate。- 它通过定义一些变量来管理定时器和执行结果。
- 在返回的函数内部,获取当前上下文和参数。如果存在定时器则清除它。如果设置了立即执行且当前没有定时器,就立即执行函数并设置定时器,否则直接设置定时器延迟执行函数。最后返回执行结果。
-
getUserAction函数用于更新页面上某个元素的内容。 -
最后对
getUserAction进行了防抖处理,设置了防抖时间为 1000 毫秒且立即执行一次。
当 immediate 参数为 true 时,debounce 函数会在触发事件后立即执行一次被包装的函数,然后在延迟指定的时间后,如果没有再次触发事件,才会再次执行函数。如果在延迟期间再次触发了事件,则会重新开始计时,直到延迟时间结束后再次执行函数。
当 immediate 参数为 false 时,debounce 函数会在触发事件后延迟指定的时间,如果在延迟期间没有再次触发事件,才会执行被包装的函数。如果在延迟期间再次触发了事件,则会重新开始计时,直到延迟时间结束后才执行函数。
结尾
总的说来,这一次的防抖相比于基础版,其实就是添加了一个立即执行效果,弄懂这个效果并能够独立实现便足以让面试官眼前一亮了。