前言
在前端開發(fā)當(dāng)中我們經(jīng)常會綁定一些事件觸發(fā)的某些程序執(zhí)行,有時(shí)這些事件會連續(xù)觸發(fā)模叙,如瀏覽器窗口 scroll、resize,輸入框的 keyup抵乓、input男杈,以及 click 事件在連續(xù)點(diǎn)擊時(shí)連續(xù)發(fā)送請求等等柑司,這些情況有些會嚴(yán)重影響前端性能,有些會增加服務(wù)器壓力景醇,使用戶體驗(yàn)大打折扣,而函數(shù)節(jié)流和防抖就是為了解決這樣的問題吝岭。
函數(shù)節(jié)流 throtter
函數(shù)節(jié)流:當(dāng)持續(xù)發(fā)生事件時(shí)三痰,保證在一個(gè)固定的時(shí)間間隔只執(zhí)行一次真正的事件處理程序,通俗的說就像 “節(jié)流” 的名字一樣窜管,打開水龍頭時(shí)要秉承勤儉節(jié)約的原則散劫,把閥門關(guān)小,最好是達(dá)到在固定間隔內(nèi)水一滴一滴的往下流幕帆。
節(jié)流函數(shù)的時(shí)序圖
從圖中可以看出获搏,連續(xù)觸發(fā)事件時(shí),真正執(zhí)行事件處理程序的間隔是固定的蜓肆,多次觸發(fā)颜凯,也只會在某一個(gè)時(shí)間間隔內(nèi)觸發(fā)一次谋币,由于事件處理函數(shù)內(nèi)部執(zhí)行邏輯各不相同,我們就封裝一版可通用的節(jié)流函數(shù)症概。
節(jié)流函數(shù)的封裝
文件:throtter.js
// 節(jié)流函數(shù)
const throtter = (func, delay = 60) => {
// 鎖的標(biāo)識
let lock = false;
// 返回一個(gè)事件處理函數(shù)
return (...args) => {
// 如果 lock 為 true 則跳出
if (lock) return;
// 執(zhí)行函數(shù)并更改鎖的狀態(tài)
func(...args);
lock = true;
// 添加定時(shí)器蕾额,在到達(dá)時(shí)間間隔時(shí)重置鎖的狀態(tài)
setTimeout(() => lock = false, delay);
}
}
throtter 函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)為在事件觸發(fā)時(shí)真正要執(zhí)行的函數(shù)彼城,第二個(gè)參數(shù)為定義的間隔時(shí)間诅蝶,在函數(shù)執(zhí)行時(shí)定義了 lock 的初始值,通過閉包返回一個(gè)函數(shù)作為事件處理函數(shù)募壕,在返回的函數(shù)內(nèi)部判斷 lock 狀態(tài)并確定執(zhí)行真正的函數(shù) func 還是跳出调炬,每次執(zhí)行 func 后會更改 lock 狀態(tài),通過定時(shí)器在規(guī)定的時(shí)間間隔內(nèi)重置 lock舱馅,這就是函數(shù)節(jié)流的原理缰泡。
驗(yàn)證節(jié)流函數(shù)
文件:throtter-test.js
// 使用節(jié)流函數(shù)
document.addEventListener("scroll", throtter(console.log));
上面我們給 document 對象添加了滾動(dòng)事件,并不斷的打印事件對象代嗤,事件處理函數(shù)的默認(rèn)參數(shù)為事件對象棘钞,從執(zhí)行效果應(yīng)該可以看出,平均每 60ms 才會觸發(fā)一次事件干毅,達(dá)到了優(yōu)化性能的目的宜猜,如果想讓真正執(zhí)行的函數(shù) func 傳入更多的參數(shù),只需如下處理硝逢。
文件:throtter-test.js
// a b 為函數(shù)要傳入的參數(shù)
let a = 1, b = 2;
// 返回事件處理函數(shù)
const func = throtter(console.log);
// 添加事件監(jiān)聽
document.addEventListener("scroll", e => func(e, a, b));
節(jié)流函數(shù)一般用于 scroll姨拥、resize 事件的情況較多,因?yàn)檫@些事件的觸發(fā)是連續(xù)性的渠鸽,需要在一個(gè)時(shí)間間隔內(nèi)只觸發(fā)一次叫乌。
函數(shù)防抖 debounce
函數(shù)防抖:當(dāng)持續(xù)發(fā)生事件時(shí),事件只在上一次觸發(fā)后的一段時(shí)間內(nèi)沒再觸發(fā)事件拱绑,才會真正的執(zhí)行事件處理邏輯综芥,如果每兩次觸發(fā)的間隔小于這個(gè)時(shí)間,則不執(zhí)行事件邏輯猎拨。
防抖函數(shù)的時(shí)序圖
從圖中可以看出膀藐,連續(xù)觸發(fā)事件時(shí)并沒有執(zhí)行事件處理函數(shù),只有在某一階段連續(xù)觸發(fā)后的最后一次才執(zhí)行红省,也就是上一次觸發(fā)的時(shí)間間隔要大于設(shè)定值才執(zhí)行额各,同樣的,事件處理函數(shù)內(nèi)部執(zhí)行邏輯各不相同吧恃,我們就封裝一版可通用的防抖函數(shù)虾啦。
防抖函數(shù)的封裝
文件:debounce.js
// 防抖函數(shù)
const dobounce = (func, delay = 300, timer = null) => {
return (...args) => {
// 清除定時(shí)器
clearInterval(timer);
// 在定時(shí)器到時(shí)后執(zhí)行事件處理函數(shù)
timer = setTimeout(() => func(...args), delay);
}
}
dobounce 函數(shù)有三個(gè)參數(shù),第一個(gè)參數(shù)為在事件觸發(fā)時(shí)真正要執(zhí)行的函數(shù),第二個(gè)參數(shù)為執(zhí)行事件的延遲時(shí)間傲醉,第三個(gè)參數(shù)為定時(shí)器 ID 的初始值蝇闭,執(zhí)行 dobounce 通過閉包返回了事件處理函數(shù),在處理函數(shù)內(nèi)部先清除定時(shí)器硬毕,然后定義定時(shí)器并將 ID 賦值給 timer呻引,如果事件連續(xù)觸發(fā),則會不斷的清除定時(shí)器吐咳,直到有一次觸發(fā)間隔超過了設(shè)定延時(shí)時(shí)間 delay逻悠,才會真正執(zhí)行 func。
驗(yàn)證防抖函數(shù)
文件:index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>函數(shù)防抖</title>
</head>
<body>
<input type="text" id="ipt">
</body>
</html>
文件:debounce-test.js
// 使用防抖函數(shù)
let ipt = document.querySelector("#ipt");
// 添加事件監(jiān)聽
ipt.addEventListener("keyup", debounce(console.log));
上面的功能跟 throtter 類似韭脊,真正執(zhí)行事件處理函數(shù)時(shí)打印事件對象童谒,通過驗(yàn)證,連續(xù)輸入觸發(fā) keyup 事件沪羔,上一次觸發(fā)和下一次觸發(fā)間隔時(shí)間必須大于 300ms 才會執(zhí)行打印事件對象的邏輯饥伊,如果想傳入多個(gè)參數(shù)套路相同。
文件:debounce-test.js
// 獲取 dom 元素
let ipt = document.querySelector("#ipt");
// a b 為函數(shù)要傳入的參數(shù)
let a = 1, b = 2;
// 返回事件處理函數(shù)
const func = debounce(console.log);
// 添加事件監(jiān)聽
ipt.addEventListener("keyup", e => func(e, a, b));
防抖函數(shù)一般用于輸入框事件蔫饰,常用場景就是搜索或查詢撵渡,如果不使用防抖會連續(xù)發(fā)送請求,增加服務(wù)器的壓力死嗦,使用防抖后,會在用戶輸入要查詢的關(guān)鍵詞后才發(fā)送請求粒氧,這也更符合用戶的習(xí)慣越除,例如百度搜索,就是這樣實(shí)現(xiàn)的外盯。
總結(jié)
“節(jié)流” 和 “防抖” 是前端在項(xiàng)目中經(jīng)常使用的優(yōu)化手段摘盆,代碼雖然不多,但是確是前端面試 “出鏡率” 非常高的知識點(diǎn)饱苟,從而能看出它們的重要性孩擂,所以建議前端同學(xué)們一定要知道,并能手寫箱熬,這是 “一箭雙雕” 的事类垦,可以用來通過面試,也可以因?yàn)楣ぷ髦杏龅街苯泳蛯懚岣吖ぷ餍省?/p>