在日常開發(fā)中栗竖,我們經(jīng)常能夠碰到以下工作場景:
- 對提交按鈕進行變態(tài)的點擊壓力測試
- 輸入框內(nèi)容的實時校驗(譬如驗證用戶名是否已存在)
- 圖片滾動加載
scroll
操作 - 窗口放大縮小
resize
操作 - 對某一區(qū)域進行
mousemove
操作
- 圖片滾動加載
上述頻繁觸發(fā)事件的操作
,如果我們不采取任何操作长赞,勢必會造成極差的用戶體驗。譬如态贤,對提交按鈕連續(xù)點擊發(fā)起請求,會給服務(wù)器帶來壓力醋火;窗口放大縮小悠汽,會連續(xù)觸發(fā)瀏覽器的resize
函數(shù),如果涉及到大量的dom操作
芥驳,勢必又會引起頁面的回流與重繪
柿冲,有可能會讓頁面變得卡頓等等。
如果我們不想頻繁的觸發(fā)某一事件兆旬,這時候就可以考慮用函數(shù)防抖假抄、函數(shù)節(jié)流
了。
函數(shù)防抖(debounce)
原理:在規(guī)定的時間t
內(nèi),如果連續(xù)觸發(fā)某一事件丽猬,則不會調(diào)用事件回調(diào)函數(shù)慨亲;連續(xù)觸發(fā)某一事件,t
時間內(nèi)宝鼓,不再
觸發(fā)該事件刑棵,則執(zhí)行事件回調(diào)函數(shù)。
我們以連續(xù)點擊提交按鈕為例:
- 正常操作:
<button id="submit">提交</button>
<script>
function doAjax() {
console.log("Todo ajax...");
}
var btn = document.querySelector("#submit");
btn.addEventListener("click", doAjax);
</script>
在短時間內(nèi)愚铡,我們對提交按鈕連續(xù)點擊蛉签,可以看到連續(xù)的請求被發(fā)起,我想這個時候后端的同事應(yīng)該會淚流滿面吧沥寥。
- 使用防抖
<button id="submit">提交</button>
<script>
function doAjax() {
console.log("Todo ajax...");
}
function debounce(fn, delay) {
var timer = null;
return function() {
timer && clearTimeout(timer);
var context = this, // 將執(zhí)行環(huán)境指向當(dāng)前dom
arg = arguments; // 事件e
timer = setTimeout(function() {
fn.call(context, arg);
},delay);
}
}
var btn = document.querySelector("#submit");
btn.addEventListener("click", debounce(doAjax, 1000));
</script>
我們可以觀察到碍舍,連續(xù)點擊提交按鈕,并沒有執(zhí)行請求邑雅;隔了1s后片橡,執(zhí)行請求(也就是在這1s內(nèi),沒有點擊提交按鈕)淮野,這樣就很好的解決了我們的煩惱捧书。
- 防抖之立即執(zhí)行
上述的防抖函數(shù),已經(jīng)可以解決我們大部分場景下的問題骤星,但有一個需要注意的點:點擊該提交按鈕经瓷,需要等一段時間后,才會調(diào)用函數(shù)洞难。而我們工作當(dāng)中的另外一種需求為:點擊后立即執(zhí)行舆吮,在接下來的連續(xù)觸發(fā)中,不執(zhí)行事件回調(diào)函數(shù)
。
<button id="submit">提交</button>
<script>
function doAjax() {
console.log("Todo ajax...");
}
function debounce(fn, delay, isImmediate) {
var timer = null;
return function() {
timer && clearTimeout(timer);
var context = this, // 將執(zhí)行環(huán)境指向當(dāng)前dom
arg = arguments; // 事件e
if(isImmediate) {
!timer && fn.call(context, arg); // timer為null(即沒有被執(zhí)行過色冀,或被重置)
// 立即執(zhí)行潭袱,后續(xù)連續(xù)點擊不起作用
timer = setTimeout(function() {
timer = null;
}, delay);
} else {
timer = setTimeout(function() {
fn.call(context, arg);
},delay);
}
}
}
var btn = document.querySelector("#submit");
btn.addEventListener("click", debounce(doAjax, 1000, true));
</script>
從上圖我們可以觀察到:第一次點擊,請求被執(zhí)行锋恬,后續(xù)連續(xù)的點擊操作都不被執(zhí)行敌卓;等過了1s后,再次點擊伶氢,請求被執(zhí)行。
函數(shù)節(jié)流(throttle)
原理:連續(xù)觸發(fā)某一事件瘪吏,會固定每隔一段時間執(zhí)行一次事件回調(diào)函數(shù)(區(qū)別于防抖的連續(xù)觸發(fā)癣防,不執(zhí)行事件回調(diào)函數(shù))
這里,我們以mousemove
事件舉例:
- 正常操作
<div id="box"></div>
<script>
var box = document.getElementById("box");
var count = 1;
function doAction() {
box.innerText = count++;
}
box.addEventListener("mousemove", doAction);
</script>
可以看到掌眠,我們的小鼠標蕾盯,輕輕一劃,連續(xù)觸發(fā)了不知道多少次
mousemove
事件蓝丙,如果這里涉及到復(fù)雜的ajax操作级遭,那又要悲劇了,ε(┬┬﹏┬┬)3渺尘!
- 節(jié)流之時間戳實現(xiàn)
<div id="box"></div>
<script>
var box = document.getElementById("box");
var count = 1;
function doAction() {
box.innerText = count++;
}
function throttle(fn, delay) {
var start = new Date();
return function() {
var context = this, // 將執(zhí)行環(huán)境指向當(dāng)前dom
arg = arguments; // 事件e
var current = new Date();
if(current - start >= delay) {
fn.call(context, arg);
start = current;
}
}
}
box.addEventListener("mousemove", throttle(doAction, 1000));
由上圖我們可以觀察到:在藍色塊內(nèi)挫鸽,連續(xù)觸發(fā)mousemove
事件,數(shù)字以恒定速率(這里是1s)出現(xiàn)鸥跟。
- 節(jié)流之定時器實現(xiàn)
<div id="box"></div>
<script>
var box = document.getElementById("box");
var count = 1;
function doAction() {
box.innerText = count++;
}
function throttle(fn, delay) {
var timer = null;
return function() {
var context = this, // 將執(zhí)行環(huán)境指向當(dāng)前dom
arg = arguments; // 事件e
if(!timer) {
timer = setTimeout(function() {
timer = null;
fn.call(context, arg);
},delay);
}
}
}
box.addEventListener("mousemove", throttle(doAction, 1000));
觀察上圖丢郊,在藍色塊內(nèi),連續(xù)觸發(fā)
mousemove
事件医咨,數(shù)字以恒定速率出現(xiàn)枫匾。
二者區(qū)別如下:
- 時間戳版會在開始時立即執(zhí)行一次,最后時間間隔內(nèi)不再執(zhí)行拟淮;(注冊事件函數(shù)的時候干茉,會執(zhí)行一次,拿到初始時間很泊,等你
mousemove
的時候角虫,時間間隔肯定遠大于你的delay
時間,或者你不等待委造,直接觸發(fā)mousemove
事件) - 定時器版開始時不執(zhí)行上遥,最后時間間隔內(nèi)再執(zhí)行一次。
總結(jié)
防抖與節(jié)流的區(qū)別
函數(shù)防抖好比是公交車驼浚靠在站臺后粉楚,乘客源源不斷地上車,但司機只會等所有乘客上車之后,才發(fā)車模软。
函數(shù)節(jié)流好比是你每天都會喝水伟骨,但是你不會一喝水就上廁所,而是每隔一段時間就去上廁所燃异。
應(yīng)用場景區(qū)別
函數(shù)節(jié)流不管事件觸發(fā)有多頻繁携狭,都會保證在規(guī)定時間內(nèi)一定會執(zhí)行一次真正的事件處理函數(shù),而函數(shù)防抖只是在最后一次事件后才觸發(fā)一次函數(shù)回俐。 比如在頁面的無限加載場景下逛腿,我們需要用戶在滾動頁面時,每隔一段時間發(fā)一次 Ajax 請求仅颇,而不是在用戶停下滾動頁面操作時才去請求數(shù)據(jù)单默。這樣的場景,就適合用節(jié)流技術(shù)來實現(xiàn)忘瓦。
參考文獻
司徒正美-函數(shù)防抖與函數(shù)節(jié)流
蝦扯蛋之函數(shù)防抖和節(jié)流
前端麻辣燙-JS函數(shù)節(jié)流與防抖