前提
- 防抖和节流的出现是为了让一个函数不这么频繁的发生,有时候无意义的函数(比如:请求数据)发生的太频繁会对服务器造成巨大的压力
具体案例
//当按下键盘的时候会持续触发这个事件
//需求:我们并不需要每次按下就触发,用户停止按下之后再触发这个事件
//具体化需求:可以再用户停止按键盘2s之后再触发,如果.5s内还按键盘的话不触发事件
<input id="ipt1" />
ipt1.onkeydown = function(){
console.log(e.target.value);
}
简易版防抖
- 原理:利用定时器来实现,小提示:定时器的变量是以一个自增的数字显示
let ipt1 = document.querySelector("#ipt1");
//设定一个变量,用来存储定时器
let timer = null
ipt1.onkeydown = function(e){
//如果之前有定时器的话就取消定时器
timer && clearTimeout(timer);
//将延迟0.5s的定时器保存到timer里面
timer = setTimeout(()=>{
console.log(timer);//1,2,3...
console.log(e.target.value);
},500)
}
让我们来看一下具体过程:
- 第一次触发onkeydown时,开启了一个0.5s的a定时器
- 如果在0.5s触发第二个onkeydown,那么a定时器被取消, 触发b定时器(新的定时器)
- 如果第三个onkeydown在0.5内触发(b定时器开启之后的.5s内),那么重复No.2步骤;如果第三个onkeydown在0.5s之后触发,那么久会触发事件执行
防抖-非立即执行
- 那么,简易的完成了,就来封装防抖函数。
let ipt1 = document.querySelector("#ipt1");
//对防抖函数的封装
//传入2个参数,一个为keydown的函数,另一个为延迟的时间
function debounce(fn,wait){
let timer;
//用到了闭包的原理
return function(){
timer && clearTimeout(timer);
//如果有函数传过来,再这里收集
let args = [...arguments];
//防止this指向错误
let context = this;
timer = setTimeout(()=>{
//用this的显式绑定来运行函数
fn.apply(this,args)
},wait)
}
}
//keydown的函数
function trigger(e){
console.log(e.target.value);
}
let fn = debounce(trigger,500)
//不传参可以直接这样写,
ipt1.onkeydown = fn
//传参的话用一个函数包裹
ipt1.onkeydown = function(){
fn('123')
}
防抖-立即执行
- 上面的函数分析发现,其实是个非立即执行版本(按下键盘0.5s后再触发)
- 那么现在来实现一个立即执行版本(按下键盘之后触发)
function debounce(fn,wait){
let timer;
return function(){
timer && clearTimeout(timer)
let context = this;
let args = [...arguments];
//这里设置了一个变量,取反timer
let doNow = !timer;
timer = setTimeout(()=>{
//定时器里面改变的timer
timer = null;
},wait)
//根据定时器的反变量来决定是否执行函数
if(doNow){
fn.apply(context,this)
}
}
}
- 这里大家可以自己分析一下步骤过程,相信对你理解内部运行有很大的帮助
合并方案
- 原理:根据immediate变量来选择非立即执行还是立即执行版本
function finallyDebounce(fn,wait,immediate){
let timer;
return function(){
timer && clearTimeout(timer)
let args = [...arguments]
let context = this;
if(immediate){
const Now = !timer;
timer = setTimeout(()=>{
timer = null;
},wait)
if(Now){
fn.apply(context,args)
}
}else{
timer = setTimeout(()=>{
fn.apply(context,args)
},wait)
}
}
}
节流函数
- 个人理解:防抖和节流其实根据wait的设置可以达到一样效果,但是,防抖一般用来处理,很多触发,最后只触发一次,而节流主要达到再规定的时间内触发一次(可以有n个规定的时间即触发n次)
节流-立即执行
- 原理:通过时间戳来实现
function throttle(fn,wait){
let time = 0
return function(){
//选取现在的时间戳
let now = Date.now();
let args = [...arguments];
let context = this;
//因为now-time = now肯定大于wait
//所以函数会先执行一次
if(now - time > wait){
fn.apply(context,args)
time = now
}
}
}
节流-非立即执行
function throttle(fn,wait){
let timer;
return function(){
let args = [...arguments];
let context = this;
//为了实现在规定的时间内必须执行一次!
if(!timer){
timer = setTimeout(()=>{
fn.apply(context,this)
timer = null
},wait)
}
}
}
双剑合并
function finalyThrottle(fn,wait,immediate){
if(immediate){
let time = 0;
return function(){
let args = [...arguments];
let context = this;
let now = Date.now()
if(now - time > wait){
fn.apply(context,args)
time = now;
}
}
}else{
let timer
return function(){
let args = [...arguments];
let context = this;
if(!timer){
timer = setTimeout(()=>{
fn.apply(context,args);
timer = null;
},wait)
}
}
}
}