js 的函數(shù)節(jié)流(throttle)和函數(shù)防抖(debounce)概述
函數(shù)防抖(debounce)
一個(gè)事件頻繁觸發(fā)有额,但是我們不想讓他觸發(fā)的這么頻繁形娇,于是我們就設(shè)置一個(gè)定時(shí)器讓這個(gè)事件在 xxx 秒之后再執(zhí)行哆键。如果 xxx 秒內(nèi)觸發(fā)了,則清理定時(shí)器,重置等待事件 xxx 秒
比如在拖動(dòng) window 窗口進(jìn)行 background 變色的操作的時(shí)候,如果不加限制的話亭螟,隨便拖個(gè)來回會(huì)引起無限制的頁(yè)面回流與重繪
或者在用戶進(jìn)行 input 輸入的時(shí)候,對(duì)內(nèi)容的驗(yàn)證放在用戶停止輸入的 300ms 后執(zhí)行(當(dāng)然這樣不一定好骑歹,比如銀行卡長(zhǎng)度驗(yàn)證不能再輸入過程中及時(shí)反饋)
一段代碼實(shí)現(xiàn)窗口拖動(dòng)變色
<script>
let body = document.getElementsByTagName("body")[0];
let index = 0;
if (body) {
window.onresize = function() {
index++;
console.log("變色" + index + "次");
let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")"; //晃瞎眼睛
};
}
</script>
簡(jiǎn)單的防抖
使用 setTimeout 進(jìn)行延遲處理预烙,每次觸發(fā)事件時(shí)都清除掉之前的方法
<script>
let body = document.getElementsByTagName("body")[0];
let index = 0;
let timer = null;
if (body) {
window.onresize = function() {
//如果在一秒的延遲過程中再次觸發(fā),就將定時(shí)器清除,清除完再重新設(shè)置一個(gè)新的
clearTimeout(timer);
timer = setTimeout(function() {
index++;
console.log("變色" + index + "次");
let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
}, 1000); //反正就是等你什么都不干一秒后才會(huì)執(zhí)行代碼
};
}
</script>
抽離 debounce
但是目前有一個(gè)問題道媚,就是代碼耦合扁掸,這樣不夠優(yōu)雅(b 格),將防抖和變色分離一下
<script>
let body = document.getElementsByTagName("body")[0];
let index = 0;
let lazyLayout = debounce(changeBgColor, 1000);
window.onresize = lazyLayout;
function changeBgColor() {
index++;
console.log("變色" + index + "次");
let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
}
//函數(shù)去抖(連續(xù)事件觸發(fā)結(jié)束后只觸發(fā)一次)
function debounce(func, wait) {
let timeout, context, args; //默認(rèn)都是undefined
return function() {
context = this;
args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(function() {
//執(zhí)行的時(shí)候到了
func.apply(context, args);
}, wait);
};
}
</script>
underscore.js 的 debounce
underscore.js 實(shí)現(xiàn)的 debounce 已經(jīng)經(jīng)過人民群眾的檢驗(yàn)
//1.9.1
_.debounce = function(func, wait, immediate) {
var timeout, result;
var later = function(context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
var debounced = restArguments(function(args) {
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
} else {
timeout = _.delay(later, wait, this, args);
}
return result;
});
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
函數(shù)節(jié)流(throttle)
簡(jiǎn)單的節(jié)流
一個(gè)事件頻繁觸發(fā)最域,但是在 xxx 秒內(nèi)只能執(zhí)行一次代碼
//上面的變色在節(jié)流中就是這樣寫了
<script>
let doSomething = true;
let body = document.getElementsByTagName("body")[0];
let index = 0;
window.onresize = function() {
if (!doSomething) return;
doSomething = false;
setTimeout(function() {
index++;
console.log("變色" + index + "次");
let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
doSomething = true;
}, 1000);
};
</script>
分離出 throttle 函數(shù)
跟上面的防抖差不多谴分,隨便分離一下,降低代碼的耦合度
<script>
let body = document.getElementsByTagName("body")[0];
let index = 0;
let lazyLayout = throttle(changeBgColor, 1000);
window.onresize = lazyLayout;
function changeBgColor() {
index++;
console.log("變色" + index + "次");
let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
}
//函數(shù)去抖(連續(xù)事件觸發(fā)結(jié)束后只觸發(fā)一次)
function throttle(func, wait) {
let context,
args,
doSomething = true;
return function() {
context = this;
args = arguments;
if (!doSomething) return;
doSomething = false;
setTimeout(function() {
//執(zhí)行的時(shí)候到了
func.apply(context, args);
doSomething = true;
}, wait);
};
}
</script>
underscore.js 中 throttle 函數(shù)實(shí)現(xiàn)
_.throttle = function(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = _.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};
防抖和節(jié)流是用來干啥的镀脂?
防抖的用處
- 綁定 scroll 滾動(dòng)事件,resize 監(jiān)聽事件
- 鼠標(biāo)點(diǎn)擊牺蹄,執(zhí)行一個(gè)異步事件,相當(dāng)于讓用戶連續(xù)點(diǎn)擊事件只生效一次(很有用吧)
- 還有就是輸入框校驗(yàn)事件(但是不一定好使薄翅,比如校驗(yàn)銀行卡長(zhǎng)度沙兰,當(dāng)你輸入完之后已經(jīng)超出 100 個(gè)字符虑省,正常應(yīng)該是超出就提示錯(cuò)誤信息)
節(jié)流的用處
- 當(dāng)然還是鼠標(biāo)點(diǎn)擊啦,但是這個(gè)是限制用戶點(diǎn)擊頻率僧凰。類似于你拿把 ak47 射擊探颈,槍的射速是 100 發(fā)/分鐘,但是的手速達(dá)到 1000 按/分鐘,就要限制一下嘍(防止惡意刷子)
- 根據(jù)屏幕滾動(dòng)到底部加載更多的功能
其實(shí)二者主要就是為了解決短時(shí)間內(nèi)連續(xù)多次重復(fù)觸發(fā)和大量的 DOM 操作的問題训措,來進(jìn)行性能優(yōu)化(重點(diǎn)是同時(shí)還能接著辦事伪节,并不耽誤)
防抖主要是一定在 xxx 秒后執(zhí)行,而節(jié)流主要是在 xxx 內(nèi)執(zhí)行(時(shí)間之后,時(shí)間之內(nèi))
右邊那個(gè)快速目錄就是加了個(gè) throttle绩鸣,控制臺(tái)的執(zhí)行速度就減少了(快速目錄是看了掘金的目錄之后弄的怀大,確實(shí)方便了好多,對(duì)于長(zhǎng)文本的閱讀體驗(yàn)好了不少)
文章寫的時(shí)候用的 underscore 1.8.2 版本呀闻,實(shí)現(xiàn)也是參考 underscore 的源碼化借,實(shí)現(xiàn)方式與 underscore 最新有些代碼還是不太一樣了。(功能還是相同的)