应用场景
防抖和节流是针对高频触发而出现的控制触发频率的解决方案(属于性能优化方案)。
如: 如鼠标移动事件onmousemove, 滚动滚动条事件onscroll,窗口大小改变事件onresize,监听输入框oninput事件。。。。
防抖(debounce)
我先来说一个常见的场景:input输入框远程搜索功能,这里则需要监听oninput事件触发后请求接口。 这时候会出现一个问题,用户输入搜索内容是一个字一个输入的,只要input框内容改变就请求一次接口。其实只有最后一次输入的内容才是用户想要搜索的,这导致了服务器资源的浪费。
这时候就进行改造,目标为:用户输入过程中不触发请求,停止输入400毫秒后再触发。 这时候就用到了防抖(debounce) 。
版本1(延迟执行): 周期内有新事件触发,清除旧定时器,重置新定时器。
// 策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。
var debounce = (fn, wait) => {
let timer, // 定时器id
timeStamp=0, // 最近一次执行时间
context,// 执行上下文
args;
let run = ()=>{
timer= setTimeout(()=>{
fn.apply(context,args);
},wait);
}
let clean = () => {
clearTimeout(timer);
}
return function(){
context=this;
args=arguments;
let now = (new Date()).getTime();
// 将当前时间与上次执行时间差 与设置的间隔时间比较
if(now-timeStamp < wait){
console.log('重置',now);
clean(); // 清除定时器
run(); // 从当前时间重置新的计时器
}else{
console.log('set',now);
run(); // 上次计时器已经执行,设置一个新的计时器
}
// 记录最近一次执行时间
timeStamp=now;
}
}
// 使用
document.getElementById('inp').addEventListener(
'input',
// 执行debounce函数 返回触发执行函数 形成闭包
debounce(function() {
console.log(this.value)
}, 400)
);
看效果
版本2: 周期内有新事件触发,记录最近触发时间,当前周期结束后判断当前周期内是否有触发,如有则设置延时器,以此类推,直到周期结束后判断当前周期内没有触发则执行动作。(此版本不会清除定时器)
var debounce = (fn, wait) => {
let timer, startTimeStamp=0;
let context, args;
let run = (timerInterval)=>{
timer= setTimeout(()=>{
let now = (new Date()).getTime();
let interval=now-startTimeStamp
if(interval<timerInterval){ // 判断当前周期内是否被触发
console.log('debounce reset',timerInterval-interval);
startTimeStamp=now;
run(wait-interval); // 重置定时器的剩余时间
}else{
// 周期内无触发 执行动作
fn.apply(context,args);
clearTimeout(timer);
timer=null;
}
},timerInterval);
}
return function(){
context=this;
args=arguments;
let now = (new Date()).getTime();
startTimeStamp=now; // 记录最近一次触发时间
if(!timer){
console.log('debounce set',wait);
run(wait); // 上次计时器已经执行,设置一个新的计时器
}
}
}
版本3(前缘执行): 在版本二基础上添加是否立即执行选项
当事件快速连续不断触发时,动作只会执行一次,前缘debounce,是在周期开始时执行。
var debounce = (fn, wait, immediate=false) => {
let timer, startTimeStamp=0;
let context, args;
let run = (timerInterval)=>{
timer= setTimeout(()=>{
let now = (new Date()).getTime();
let interval=now-startTimeStamp
if(interval<timerInterval){ // 定时器开始时间被重置,所以interval小于timerInterval
console.log('debounce reset',timerInterval-interval);
startTimeStamp=now;
run(wait-interval); // 重置定时器的剩余时间
}else{
if(!immediate){
fn.apply(context,args);
}
clearTimeout(timer);
timer=null;
}
},timerInterval);
}
return function(){
context=this;
args=arguments;
let now = (new Date()).getTime();
startTimeStamp=now; // 记录最近一次触发时间
if(!timer){
console.log('debounce set',wait);
// 立即执行
if(immediate) {
fn.apply(context,args);
}
run(wait); // 上次计时器已经执行,设置一个新的计时器
}
}
}
节流(throttling)
在周期内只执行一次,如有新的事件触发不执行。周期结束后又有事件触发,开始新的周期。节流策略也分前缘和延迟两种。
延迟:throttling
举个栗子:监听浏览器滚动条滚动事件
// 代码如下 触发滚动事件打印滚动条位置
onscroll = function() {
console.log(this.scrollY)
}
这时会出现个情况 触发频率非常高,我就按了一下下箭头触发了九次。
如果处理逻辑发杂的话就对浏览器性能造成了很大负担,这时候节流就登场了。
// 定时器期间,触发不执行,只在定时器结束后执行
var throttling = (fn, wait) => {
let timer;
let context, args;
let run = () => {
timer=setTimeout(()=>{
// 周期结束后执行
fn.apply(context,args);
clearTimeout(timer);
timer=null;
},wait);
}
return function () {
context=this;
args=arguments;
if(!timer){
console.log("周期开始");
run();
}else{
console.log("被节流");
}
}
}
// 使用
onscroll = throttling(function() {console.log(this.scrollY)},200)
前缘throttling
// 上个版本基础上添加前缘
var throttling = (fn, wait, immediate=false) => {
let timer, timeStamp=0;
let context, args;
let run = () => {
timer=setTimeout(()=>{
// 周期结束后执行
if(!immediate){
fn.apply(context,args);
}
clearTimeout(timer);
timer=null;
},wait);
}
return function () {
context=this;
args=arguments;
if(!timer){
console.log("周期开始");
// 前缘执行
if(immediate){
fn.apply(context,args);
}
run();
}else{
console.log("被节流");
}
}
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!