防抖与节流的概念
函数防抖: 触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。具体场景可以搜索框输入关键字过程中实时 请求服务器匹配搜索结果,如果不进行处理,那么就是输入框内容一直变化,导致一直发送请求。如果进行防抖处理,结果就是当我们输入内容完成后,一定时间(比如500ms)没有再输入内容,这时再触发请求。
函数节流: 高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。具体场景可以是抢购时候,由于有无数人快速点击按钮,如果每次点击都发送请求,就会给服务器造成巨大的压力,但是我们进行节流后,就会大大减少请求的次数。
防抖函数(debounce)
动手实现第一版防抖函数:
function debounce (fn, wait) {
let timeout;
retuen function () {
if (timeout) {
clearTimeout(timeout)
}
timeout=setTimeout(fn,wait)
}
}
我们来写个demo验证一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
#search {
width: 400px;
margin-left: 300px;
margin-bottom: 50px;
}
#showSearch {
width: 800px;
height: 100px;
background: lightblue;
color: red;
margin-left: 300px;
}
</style>
</head>
<body>
<input type="search" name="" id="search" />
<div id="showSearch"></div>
</body>
<script>
function debounce(fn, wait) {
let timeout;
return function() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(fn, wait);
};
}
let search = document.querySelector("#search");
let showSearch = document.querySelector("#showSearch");
function getSearchInfo(e) {
// showSearch.innerText = this.value; // 报错
// console.log(e); // undefined
showSearch.innerText = search.value;
}
search.onkeyup = debounce(getSearchInfo, 1000);
</script>
</html>
效果:
可以看到执行上面的demo,我们输入值以后不触发keyup事件就会隔1秒钟蓝色div才会出现字,如果你一直触发是不会显示的等到你停止触发后的一秒后才显示 有同学可能对第一版代码觉得挺简单的,确实简单,不过我还是要啰嗦几句,第一版的代码中,debounce函数 返回了一个匿名函数,而这匿名函数又引用了timeout这个变量,这样就形成了闭包,也就是函数的执行上下文的活动对象并不会被销毁,保存了timeout变量,才能实现我们这个 如果你在一个事件触发的n秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行 的需求。
修复this指向和event问题
显然上述中的代码还存留了二个问题就是this问题,我们this指向的不是window对象,而是指向我们的dom对象,第二个就是event对象,event对象在这里会报undefined,那么接下来我们来完善我们的第二版代码吧:
function debounce(fn, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function() {
fn.apply(context, args);
}, wait);
};
}
第二版代码已经实现了,我们来写个demo验证下吧:
let search = document.querySelector("#search");
let showSearch = document.querySelector("#showSearch");
function getSearchInfo(e) {
showSearch.innerText = this.value;
console.log(e);
}
search.onkeyup = debounce(getSearchInfo, 1000);
效果图:
这里涉及的知识点就是this指向和arguments、apply等,先来说说arguments属性,我们知道可以通过arguments属性获取函数的参数值,而dom事件操作中,会给 回调函数(这里回调函数是debounce函数返回的闭包函数)传递一个event参数,这样我们就可以通过arguments属性拿到event属性,那么问题二就解决啦,再来说说问题一的this指向,此时这里keyup事件的回调函数是debounce函数返回的闭包函数而不是getSearchInfo函数(但是我们希望处理业务逻辑的getSearchInfo的this指向能够指向dom对象),我们知道this的指向是我最后被谁调用,我就指向谁 ,那么我这里被search调用所以「this指向search」,但是由于有setTimeout异步操作,我们getSearchInfo函数的this指向是window(非严格模式下),所以我们需要改变getSearchInfo的指向,这样我们用apply就完美实现了.
节流函数(throttle)
立即执行:
我们可以使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
function throttle(fn, wait) {
let context, args;
let previous = 0;
return function() {
let now = +new Date();
context = this;
args = arguments;
if (now - previous > wait) {
fn.apply(context, args);
previous = now;
}
};
}
demo使用:demo跟上述防抖用是一样
search.onkeyup = throttle(getSearchInfo, 3000);
效果图:
这里的节流立即执行版跟我们的防抖第一版的知识点原理一样,不再过多的赘述。
非立即执行:非立即执行就是过了n秒后我们再执行,那么我们自然而然想到「定时器」。
function throttle(fn, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
// 这里不需要清除定时器 清除了会重新计算时间
// 清除这个定时器不代表timeout为空
if (timeout) {
return false;
}
timeout = setTimeout(function() {
fn.apply(context, args);
timeout = null;
}, wait);
};
}
demo使用:
function getSearchInfo(e) {
console.log(this.value);
// 验证的效果是 showSearch多久才能读到这个this.value
showSearch.innerText = this.value;
}
search.onkeyup = throttle(getSearchInfo, 3000);
效果图:
这次的效果是等待过了三秒才执行,那我们来比较立即执行和非立即执行的效果
- 立即执行会立刻执行,非立即执行会在 n 秒后第一次执行
- 立即执行停止触发后没有办法再执行事件,非立即执行停止触发后依然会再执行一次事件
防抖和节流的区别
实际上防抖和节流都是限制一些频繁的事件触发(window 的 resize、scroll、mousedown、mousemove、keyup、keydown),但他们还是有实质性的区别的。
- 防抖是虽然事件持续触发,但只有等事件停止触发后n秒才执行函数(如果你在时间内重新触发事件,那么时间就重新算,这是防抖的特点,可以按照这个特点选择场景)。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷,司机才开车。
- 节流是持续触发的时候,每隔n秒执行一次函数比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。