防抖:
- 事件被触发n秒后执行回调,如果在这n秒内又被触发,则重新计时。
- 动作被延迟执行。事件被触发后,n秒后执行动作。
- 比如 要搜索某个字符串,基于性能考虑,肯定不能用户每输入一个字符就发送一次搜索请求。此时可以使用js防抖,在用户停止输入的一段时间后(如10ms),我们才发一次请求。
节流:
- 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
- 动作被定期执行。固定时间内,只执行一次动作,若有新事件触发,不执行。
- 比如 用户输入过程中,每过10ms就查询一次相关字符串。
防抖和节流的区别:
- 同样是2s的等待周期,防抖是只要触发间隔在2s以内,它就重新计时;
- 节流是,2s给予一次执行时间函数机会,还能设置第一次和最后一次的操作是否执行。
防抖和节流策略的选择:
- 如果事件触发是高频但是有停顿时,可以选择防抖;
- 在事件连续不断高频触发时,只能选择节流,因为防抖可能会导致动作只被执行一次,界面出现跳跃。
1、防抖
1.1 未使用防抖的情况
1)以输入框的keyup事件为例。可以看到,每秒都会执行多次console事件。
解决:可以使用js防抖,在用户停止输入的一段时间后(如500ms)触发判断
1.2 使用防抖后
可以看到,如果用户不断输入,则不会触发console事件,这是由于在规定的 500ms 时间间隔内重新触发了keyup事件,定时器重新开始计时。当用户在500ms的间隔内没有进行输入,则会触发事件。适用于实时判断用户输入的内容(如邮箱、电话)是否符合格式要求等场景。
1.3 防抖的实现
思想:每次触发事件时都取消之前的延时调用方法。
当用户触发点击事件 delay ms后,我们才发请求。所以我们每次都使用一个定时器来保存用户的操作,然后每次在延迟时间内触发点击事件时,都把上一次的定时器清除掉即可,这样就保证了延迟delay ms 后,只发一次请求。
//1. 实现防抖函数。触发事件 delay ms 后,再执行动作fn
function debounce(fn,delay){
let timeout = null; //1) 存放定时器返回值,便于清除定时器
return function(){
clearTimeout(timeout); // 2) 每次触发事件时,把前一个事件的定时器清除掉
timeout = setTimeout(() => {
fn.apply(this, arguments); // 3) delay ms后执行动作fn。this指向触发该事件的div
},delay)
}
}
// 定义动作
function sayHi(){
console.log("防抖成功");
}
// 触发box的点击事件.(为box添加点击事件,点击时执行debounce返回的函数)
let box = document.getElementById('box');
box.addEventListener('click', debounce(sayHi, 500));
<div id="box" style="height: 100px; width: 100px; background: cadetblue;"></div>
点击完一次,500ms后打印“防抖成功”。
多次点击时,以最后一次点击结束开始计时,500ms后打印“防抖成功”。
2、节流
2.1 节流的使用场景
下面情况需要使用节流去解决:
1)window的 resize 和 scroll 事件
如:滚动到页面底部加载更多,稍微滚动一下就会触发n多次scroll事件,如果每次触发scroll事件的时候都去判断一次是否已经滚动到了页面底部,无疑会造成资源的浪费。此时若使用js节流,每隔一定的时间(如500ms)进行一次判断,间隔期间只能有一次触发判断,既节省了资源,也不会影响用户体验。
2)鼠标不断点击触发,mousedown(单位时间内只触发一次)
如:点击提交按钮提交表单信息,不小心连续点了多次,就提交了多次。
解决:使用js节流定期执行,固定周期内,只提交一次。
2.2 节流的实现
高频事件触发,但在n秒内只会执行一次,所以节流会减少函数的执行频率
思路:每次触发事件时都判断当前是否有等待执行的延时函数
//1. 实现节流函数。每delay ms 执行一次动作fn
function throttle(fn, delay){
var runFlag = false; //1) 判断当前是否有等待执行的延时函数。false: 没有等待执行的函数
return function(){
if(runFlag) { //2) 之前有等待执行的函数。由于delay内,只执行一次,故直接返回。
return false;
};
runFlag = true; //3) 当前的函数等待执行
setTimeout(() => {
fn.apply(this, arguments); //4) 过了delay后,执行动作fn。this指向被点击的div
runFlag = false; //5) 当前函数执行完。没有等待执行的函数
}, delay)
}
}
// 定义动作
function sayHi(){
console.log("节流成功");
}
// 触发box的点击事件.(为box添加点击事件,点击时执行throttle返回的函数)
let box = document.getElementById('box');
box.addEventListener('click', throttle(sayHi, 1000));
在delay周期内,不管点击多少次,只有第一次点击时触发的动作才会在delay时间后执行