1. 防抖
1.1 什么是防抖?
? 防抖是觸發(fā)高頻事件后,n秒內(nèi)函數(shù)只會(huì)執(zhí)行一次, 如果n秒內(nèi)高頻事件再次觸發(fā),則會(huì)重新計(jì)算時(shí)間.
1.2 為什么需要防抖節(jié)流?
我們?cè)谄綍r(shí)開(kāi)發(fā)的時(shí)候,會(huì)有許多場(chǎng)景會(huì)頻繁觸發(fā)事件,比如搜索框?qū)崟r(shí)發(fā)送請(qǐng)求,onmousemove,resize,onscroll, 由于此類事件會(huì)頻繁觸發(fā),非常消耗瀏覽器性能和服務(wù)器性能,有時(shí)候,我們并不能或不想頻繁觸發(fā)事件,此時(shí)就需要用到函數(shù)的防抖和節(jié)流.
1.3 防抖的原理
事件響應(yīng)函數(shù)在一段事件后才執(zhí)行, 如果這段時(shí)間內(nèi)在次調(diào)用內(nèi)再次調(diào)用, 則重新計(jì)算執(zhí)行時(shí)間,當(dāng)預(yù)定時(shí)間內(nèi)沒(méi)有再次調(diào)用該函數(shù),則執(zhí)行事件處理邏輯函數(shù).
下面是簡(jiǎn)單的防抖實(shí)現(xiàn)
function debounce(fn,wait){
let timeout = null;// 保存延時(shí)器的id, 用于清除延時(shí)器
return function(){
const that = this;// 保存返回函數(shù)內(nèi)部this
const args = this.arguments// 獲取事件函數(shù)傳遞evnet對(duì)象參數(shù)
clearTimeout(timeout);//清除事件反復(fù)執(zhí)行時(shí)的前一個(gè)延時(shí)器
timeout = setTimeout(() => {
fn.apply(that,args)// 執(zhí)行事件處理邏輯函數(shù), 改變函數(shù)內(nèi)部this并傳參
},wait)
}
}
進(jìn)階版我們可以給防抖函數(shù)添加第三個(gè)參數(shù)用來(lái)立即執(zhí)行事件處理邏輯函數(shù).而且可以取消操作.
function debounce(func,wait, immediate){
let timeout,result;// result 用于接受立即執(zhí)行函數(shù)的返回值
let decounced = function(){
const args = arguments;// 獲取事件函數(shù)傳遞evnet對(duì)象參數(shù)
const that = this;// 獲取保存返回函數(shù)內(nèi)部this
clearTimeout(timeout);//清除上一個(gè)延時(shí)器, 防止內(nèi)存泄漏
if(immediate){
// 將timeout與callNow建立聯(lián)系, 當(dāng)timeout有值時(shí),此時(shí)callNow為false
// 當(dāng)timeout為null時(shí),此時(shí)callNow為true
let callNow = !timeout;// 用來(lái)判斷是否立即執(zhí)行
timeout = setTimeout(() =>{
timeout = null;
},wait);
// 當(dāng)callNow為true時(shí),立即執(zhí)行
if(callNow) result = func.apply(that,args)
}else{
// 不會(huì)立即執(zhí)行
timeout = setTimeout(() => {
func.apply(that, args)
}, wait)
}
// 返回立即執(zhí)行函數(shù)的結(jié)果
return result
}
//給返回的防抖處理函數(shù)添加一個(gè)取消防抖操作的方法
decounced.cancel = function(){
clearTimeout(timeout);// 清空延時(shí)器, 取消防抖操作
timeout = null;// 由于上面的代碼形成了閉包, 所有得手動(dòng)清空timeout變量, 防止內(nèi)存泄漏
}
return decounced; // 返回防抖處理函數(shù)
1.4 防抖的應(yīng)用場(chǎng)景
- 搜索框輸入查詢
- 表單驗(yàn)證
- 按鈕提交事件
- 瀏覽器滾動(dòng)事件onscroll觸發(fā)
- 瀏覽器窗口縮放, resize事件
2. 節(jié)流
2.1 什么是節(jié)流?
節(jié)流指當(dāng)高頻事件觸發(fā)時(shí),稀釋函數(shù)的執(zhí)行頻率,讓其只會(huì)在n秒內(nèi)執(zhí)行一次.
映射到在我們?nèi)粘I钪?我們咀嚼食物的頻率是非踌挪剩快的,但我們不可能咀嚼一口就把食物咽下去,我們通常只會(huì)在咀嚼的幾秒后,才把食物咽下去.此時(shí)就相當(dāng)于節(jié)流.
思路: 利用時(shí)間戳 第一版
我們通過(guò)每次事件響應(yīng)函數(shù)觸發(fā)時(shí)計(jì)算,當(dāng)前的時(shí)間戳與老的時(shí)間戳的差,判斷是否大于需要等待的時(shí)間,此時(shí)就觸發(fā)函數(shù),同時(shí)將新的時(shí)間戳的值賦值給老的時(shí)間戳, 用于計(jì)算下一次事件觸發(fā)的響應(yīng)時(shí)間
function throttle(func,wait){
let content,args;
// 之前的時(shí)間戳
let oldTime = 0;
return function(){
content = this;
args = arguments
// 獲取當(dāng)前時(shí)間戳
let nowTime = new Date().valueOf();
//如果現(xiàn)在的時(shí)間和以前的時(shí)間間隔大于等待的時(shí)間
if(nowTime - oldTime > wait){
// 立即執(zhí)行
func.apply(content,args);
oldTime = nowTime
}
}
}
我們不難發(fā)現(xiàn),上面的代碼是不顧頭,只顧尾的,也就是說(shuō)事件觸發(fā)函數(shù)的第一次會(huì)立即執(zhí)行, 最后一次不會(huì)執(zhí)行.
思路: 利用延時(shí)器setTimeout, 第二版
我們通過(guò)一個(gè)變量timeout來(lái)記錄延時(shí)器的id,由于第一次默認(rèn)值為fasle, 此時(shí)我們開(kāi)啟延時(shí)器并將其返回值賦值給timeout, 當(dāng)延時(shí)器執(zhí)行完畢時(shí),將timeout賦值為null, 使下一次判斷timeout時(shí)條件為true, 開(kāi)啟下一輪定時(shí)器.
function throttle(func,wait){
let content,args, timeout;
return function () {
content = this;
args = arguments;
// 如果timeout沒(méi)有值, 就開(kāi)啟延時(shí)器
if (!timeout) {
timeout = setTimeout(() => {
func.apply(content, args);
// 延時(shí)器執(zhí)行完之后清空timeout的值
timeout = null
}, wait);
}
}
2.2節(jié)流的應(yīng)用場(chǎng)景
- DOM元素的拖拽功能實(shí)現(xiàn)
- 射擊游戲
- 計(jì)算鼠標(biāo)移動(dòng)的距離
- 監(jiān)聽(tīng)scroll事件