函數(shù)防抖與節(jié)流
為什么要對(duì)函數(shù)進(jìn)行防抖與節(jié)流?
在進(jìn)行窗口的resize畏邢、scroll业扒、mousemove,輸入框內(nèi)容校驗(yàn)等操作時(shí)舒萎,如果這些高頻事件的處理函數(shù)調(diào)用的頻率無限制程储,會(huì)加重瀏覽器的負(fù)擔(dān),導(dǎo)致用戶體驗(yàn)非常糟糕臂寝。
此時(shí)我們可以采用debounce(防抖)和throttle(節(jié)流)的方式來減少調(diào)用頻率章鲤,同時(shí)又不影響實(shí)際效果。
防抖
函數(shù)防抖是指觸發(fā)高頻事件后n秒內(nèi)函數(shù)只會(huì)執(zhí)行一次咆贬,如果n秒內(nèi)高頻事件再次被觸發(fā)败徊,則重新計(jì)算時(shí)間
防抖的實(shí)現(xiàn)上有兩種方式:非立即執(zhí)行、立即執(zhí)行
非立即執(zhí)行版
非立即執(zhí)行版: 觸發(fā)高頻事件后過n秒執(zhí)行函數(shù)fn掏缎,如果n秒內(nèi)高頻事件再次被觸發(fā)皱蹦,則重新計(jì)算時(shí)間
function debounce1(fn, wait) {
let timeout;
return function () {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function () {
fn.apply(this, arguments);
console.log("可以再次觸發(fā)了");
}, wait);
}
}
立即執(zhí)行版
立即執(zhí)行版: 觸發(fā)高頻事件后立即執(zhí)行函數(shù)fn煤杀,過n秒后觸發(fā)高頻事件可以立即執(zhí)行函數(shù)fn,如果n秒內(nèi)高頻事件再次被觸發(fā)沪哺,則重新計(jì)算時(shí)間
function debounce2(fn, wait) {
let timeout;
return function () {
if (timeout) {
clearTimeout(timeout);
}
let callnow = !timeout;
timeout = setTimeout(function () {
timeout = null;
console.log("可以再次觸發(fā)了");
}, wait);
if (callnow) {
fn.apply(this, arguments);
}
}
}
綜合版
將兩種實(shí)現(xiàn)結(jié)合在一起可以得到綜合版的防抖怜珍。
/**
** @desc 函數(shù)防抖
** @param fn 函數(shù)
** @param wait 延遲執(zhí)行毫秒數(shù)
** @param immediate true 表立即執(zhí)行,false 表非立即執(zhí)行
*/
function debounce(fn, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) {
clearTimeout(timeout);
}
if (immediate) {
let callnow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callnow) {
fn.apply(context, args);
console.log("可以再次觸發(fā)了");
}
} else {
timeout = setTimeout(function () {
fn.apply(context, args);
console.log("可以再次觸發(fā)了");
}, wait);
}
}
}
節(jié)流
函數(shù)節(jié)流:當(dāng)連續(xù)觸發(fā)事件時(shí)凤粗,在 n 秒中只執(zhí)行一次函數(shù)酥泛。節(jié)流會(huì)稀釋函數(shù)的執(zhí)行頻率。
函數(shù)節(jié)流也有兩種實(shí)現(xiàn)方式:時(shí)間戳版嫌拣、定時(shí)器版
時(shí)間戳版
時(shí)間戳版: 在持續(xù)觸發(fā)事件的過程中柔袁,函數(shù)會(huì)立即執(zhí)行,并且每 1s 執(zhí)行一次异逐。
function throttle1(fn, wait) {
let previous = 0;
return function () {
let context = this;
let args = arguments;
let now = new Date();
if (now - previous > wait) {
fn.apply(context, args);
previous = now;
}
}
}
定時(shí)器版
定時(shí)器版: 在持續(xù)觸發(fā)事件的過程中捶索,函數(shù)不會(huì)立即執(zhí)行,并且每 1s 執(zhí)行一次灰瞻,在停止觸發(fā)事件后腥例,函數(shù)還會(huì)再執(zhí)行一次。
function throttle2(fn, wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
fn.apply(context, args);
}, wait);
}
}
}
綜合版
將兩種實(shí)現(xiàn)結(jié)合在一起可以得到綜合版的節(jié)流酝润。
/**
* @desc 函數(shù)節(jié)流
* @param fn 函數(shù)
* @param wait 延遲執(zhí)行毫秒數(shù)
* @param type "timestamp" 表時(shí)間戳版燎竖,"timeout" 表定時(shí)器版
*/
function throttle(fn, wait, type) {
if (type === "timestamp") {
var previous = 0;
} else if (type === "timeout") {
var timeout;
}
return function () {
let context = this;
let args = arguments;
if (type === "timestamp") {
let now = Date.now();
if (now - previous > wait) {
fn.apply(context, args);
previous = now;
}
} else if (type === "timeout") {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
fn.apply(context, args)
}, wait)
}
}
}
}
區(qū)別和適用場(chǎng)景
通過上面的介紹,我們可以得出防抖與節(jié)流的區(qū)別要销。
如果事件觸發(fā)頻繁构回,防抖中的計(jì)時(shí)會(huì)不斷重置,從而會(huì)不斷延遲了函數(shù)的執(zhí)行疏咐,而節(jié)流中的計(jì)時(shí)器不會(huì)因?yàn)槭录挠|發(fā)而重置纤掸。
注:以下應(yīng)用并不絕對(duì),只是寫了一下我認(rèn)為比較合適的解決方案便于理解防抖與節(jié)流的區(qū)別浑塞,具體應(yīng)用需要根據(jù)具體場(chǎng)景的需求確定借跪。
表單提交
在表單提交這一場(chǎng)景中,我們可以考慮使用立即執(zhí)行版的防抖酌壕。
用戶往往會(huì)因?yàn)樘峤粫r(shí)的等待而重復(fù)快速點(diǎn)擊按鈕進(jìn)行提交表單掏愁。顯然,這一過程中仅孩,表單的內(nèi)容不會(huì)發(fā)生改變托猩,我們只需要調(diào)用一次后臺(tái)接口。
使用立即執(zhí)行版的防抖之后辽慕,會(huì)在用戶第一次點(diǎn)擊按鈕時(shí)調(diào)用一次后臺(tái)接口京腥,如果用戶在延遲時(shí)間內(nèi)重復(fù)點(diǎn)擊,則不會(huì)執(zhí)行表單提交函數(shù)溅蛉,直至用戶最后一次點(diǎn)擊按鈕并等待超過設(shè)定的延遲時(shí)間之后再次點(diǎn)擊按鈕公浪,才會(huì)執(zhí)行表單提交函數(shù)他宛。
瀏覽器窗口滾動(dòng)事件
之前碰到過一個(gè)滾動(dòng)切換導(dǎo)航欄項(xiàng)的效果。
先獲取各個(gè)元素的位置offsetTop欠气,監(jiān)聽瀏覽器窗口滾動(dòng)事件厅各,觸發(fā)該事件時(shí),獲取滾動(dòng)條的位置scrollTop预柒,遍歷offsetTop與scrollTop進(jìn)行比較队塘,從而確定當(dāng)前位置需要高亮哪個(gè)導(dǎo)航欄項(xiàng)。
在這一場(chǎng)景中宜鸯,我們需要在多次事件觸發(fā)的過程中憔古,執(zhí)行多次函數(shù),因此淋袖,我們可以考慮使用時(shí)間戳版的節(jié)流(定時(shí)器版也可)鸿市。
減少函數(shù)的執(zhí)行頻率,防止因大量計(jì)算導(dǎo)致卡頓即碗。
參考文章
http://www.reibang.com/p/c8b86b09daf0
總結(jié)
本文主要對(duì)函數(shù)防抖與節(jié)流做了一些介紹焰情,提供了一些實(shí)現(xiàn)方法,并分析了防抖與節(jié)流的區(qū)別剥懒,舉例說明了適用場(chǎng)景内舟。
公眾號(hào):成的學(xué)習(xí)之路。跪求一波關(guān)注= =
在公眾號(hào)回復(fù) 防抖與節(jié)流蕊肥, 即可獲取源碼地址