本文摘自這里扯键,通俗易懂睦袖。防抖和節(jié)流在前端和客戶(hù)端開(kāi)發(fā)中經(jīng)常會(huì)用到,而且適用場(chǎng)景還挺多荣刑,大多數(shù)成熟的第三方庫(kù)都有提供這些功能馅笙。對(duì)于初學(xué)者來(lái)書(shū),下面可以從概念和代碼嘶摊,更清晰簡(jiǎn)單得認(rèn)識(shí)他們延蟹。
在前端開(kāi)發(fā)的過(guò)程中,我們經(jīng)常會(huì)需要綁定一些持續(xù)觸發(fā)的事件叶堆,如 resize阱飘、scroll、mousemove 等等,但有些時(shí)候我們并不希望在事件持續(xù)觸發(fā)的過(guò)程中那么頻繁地去執(zhí)行函數(shù)沥匈。
通常這種情況下我們?cè)趺慈ソ鉀Q的呢蔗喂?一般來(lái)講,防抖和節(jié)流是比較好的解決方案高帖。
讓我們先來(lái)看看在事件持續(xù)觸發(fā)的過(guò)程中頻繁執(zhí)行函數(shù)是怎樣的一種情況缰儿。
html 文件中代碼如下
<div id="content" style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>
<script>
var num = 1;
var content = document.getElementById('content');
function count() {
content.innerHTML = num++;
};
content.onmousemove = count;
</script>
在上述代碼中,div 元素綁定了 mousemove 事件散址,當(dāng)鼠標(biāo)在 div(灰色)區(qū)域中移動(dòng)的時(shí)候會(huì)持續(xù)地去觸發(fā)該事件導(dǎo)致頻繁執(zhí)行函數(shù)乖阵。效果如下
可以看到,在沒(méi)有通過(guò)其它操作的情況下预麸,函數(shù)被頻繁地執(zhí)行導(dǎo)致頁(yè)面上數(shù)據(jù)變化特別快瞪浸。所以,接下來(lái)讓我們來(lái)看看防抖和節(jié)流是如何去解決這個(gè)問(wèn)題的吏祸。
防抖(debounce)
所謂防抖对蒲,就是指觸發(fā)事件后在 n 秒內(nèi)函數(shù)只能執(zhí)行一次,如果在 n 秒內(nèi)又觸發(fā)了事件贡翘,則會(huì)重新計(jì)算函數(shù)執(zhí)行時(shí)間蹈矮。
防抖函數(shù)分為非立即執(zhí)行版和立即執(zhí)行版。
非立即執(zhí)行版:
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
非立即執(zhí)行版的意思是觸發(fā)事件后函數(shù)不會(huì)立即執(zhí)行鸣驱,而是在 n 秒后執(zhí)行泛鸟,如果在 n 秒內(nèi)又觸發(fā)了事件,則會(huì)重新計(jì)算函數(shù)執(zhí)行時(shí)間丐巫。
我們依舊使用上述綁定 mousemove 事件的例子谈况,通過(guò)上面的防抖函數(shù),我們可以這么使用
content.onmousemove = debounce(count,1000);
效果如下
可以看到递胧,在觸發(fā)事件后函數(shù) 1 秒后才執(zhí)行碑韵,而如果我在觸發(fā)事件后的 1 秒內(nèi)又觸發(fā)了事件,則會(huì)重新計(jì)算函數(shù)執(zhí)行時(shí)間缎脾。
上述防抖函數(shù)的代碼還需要注意的是 this 和 參數(shù)的傳遞
var context = this;
var args = arguments;
防抖函數(shù)的代碼使用這兩行代碼來(lái)獲取 this 和 參數(shù)祝闻,是為了讓 debounce 函數(shù)最終返回的函數(shù) this 指向不變以及依舊能接受到 e 參數(shù)。
立即執(zhí)行版:
function debounce(func,wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
}
立即執(zhí)行版的意思是觸發(fā)事件后函數(shù)會(huì)立即執(zhí)行遗菠,然后 n 秒內(nèi)不觸發(fā)事件才能繼續(xù)執(zhí)行函數(shù)的效果联喘。
使用方法同上,效果如下
雙劍合璧版:
/**
* @desc 函數(shù)防抖
* @param func 函數(shù)
* @param wait 延遲執(zhí)行毫秒數(shù)
* @param immediate true 表立即執(zhí)行辙纬,false 表非立即執(zhí)行
*/
function debounce(func,wait,immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
}
節(jié)流(throttle)
所謂節(jié)流豁遭,就是指連續(xù)觸發(fā)事件但是在 n 秒中只執(zhí)行一次函數(shù)。節(jié)流會(huì)稀釋函數(shù)的執(zhí)行頻率贺拣。
對(duì)于節(jié)流蓖谢,一般有兩種方式可以實(shí)現(xiàn)捂蕴,分別是時(shí)間戳版和定時(shí)器版。
時(shí)間戳版:
function throttle(func, wait) {
var previous = 0;
return function() {
var now = Date.now();
var context = this;
var args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
使用方式如下
content.onmousemove = throttle(count,1000);
效果如下
可以看到闪幽,在持續(xù)觸發(fā)事件的過(guò)程中啥辨,函數(shù)會(huì)立即執(zhí)行,并且每 1s 執(zhí)行一次盯腌。
定時(shí)器版
function throttle(func, wait) {
var timeout;
return function() {
var context = this;
var args = arguments;
if (!timeout) {
timeout = setTimeout(function(){
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
使用方式同上溉知,效果如下
可以看到,在持續(xù)觸發(fā)事件的過(guò)程中腕够,函數(shù)不會(huì)立即執(zhí)行级乍,并且每 1s 執(zhí)行一次,在停止觸發(fā)事件后燕少,函數(shù)還會(huì)再執(zhí)行一次卡者。
我們應(yīng)該可以很容易的發(fā)現(xiàn),其實(shí)時(shí)間戳版和定時(shí)器版的節(jié)流函數(shù)的區(qū)別就是客们,時(shí)間戳版的函數(shù)觸發(fā)是在時(shí)間段內(nèi)開(kāi)始的時(shí)候,而定時(shí)器版的函數(shù)觸發(fā)是在時(shí)間段內(nèi)結(jié)束的時(shí)候材诽。
同樣地底挫,我們也可以將時(shí)間戳版和定時(shí)器版的節(jié)流函數(shù)結(jié)合起來(lái),實(shí)現(xiàn)雙劍合璧版的節(jié)流函數(shù)脸侥。
雙劍合璧版:
/**
* @desc 函數(shù)節(jié)流
* @param func 函數(shù)
* @param wait 延遲執(zhí)行毫秒數(shù)
* @param type 1 表時(shí)間戳版建邓,2 表定時(shí)器版
*/
function throttle(func, wait ,type) {
if(type===1){
var previous = 0;
}else if(type===2){
var timeout;
}
return function() {
var context = this;
var args = arguments;
if(type===1){
var now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}else if(type===2){
if (!timeout) {
timeout = setTimeout(function(){
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
}