前言
瀏覽器中某些計(jì)算和處理要比其他的昂貴很多。例如DOM操作比DOM交互需要更多的時(shí)間和cpu時(shí)間,為了提升性能,減少DOM操作,于是,函數(shù)節(jié)流防抖和應(yīng)運(yùn)而生,其函數(shù)節(jié)流的基本思想是指,某些代碼不可以在沒(méi)有間斷的情況下連續(xù)重復(fù)執(zhí)行。函數(shù)防抖的基本思想是指,一個(gè)頻繁觸發(fā)的事情只讓最后一次執(zhí)行。下面就讓我們來(lái)認(rèn)真了解下這經(jīng)常使用的函數(shù)節(jié)流和防抖。
函數(shù)節(jié)流
函數(shù)節(jié)流:一個(gè)函數(shù)執(zhí)行一次后,只有大于設(shè)定的執(zhí)行周期后才會(huì)執(zhí)行第二次。有個(gè)需要頻繁觸發(fā)函數(shù),出于優(yōu)化性能角度,在規(guī)定時(shí)間內(nèi),只讓函數(shù)觸發(fā)的第一次生效,后面不生效。
下面主要介紹時(shí)間戳和定時(shí)器兩種方式來(lái)實(shí)現(xiàn)節(jié)流函數(shù)。
- 時(shí)間戳實(shí)現(xiàn)函數(shù)節(jié)流
根據(jù)函數(shù)節(jié)流的原理,我們也可以不依賴 setTimeout實(shí)現(xiàn)函數(shù)節(jié)流。
function throttle(fn, delay) {
// 記錄上一次函數(shù)觸發(fā)的時(shí)間
var lastTime = 0;
return function() {
// 記錄當(dāng)前函數(shù)觸發(fā)的時(shí)間
var nowTime = Date.now();
if (nowTime - lastTime > delay) {
// 修正this指向問(wèn)題
fn.call(this);
// 同步時(shí)間
lastTime = nowTime;
}
}
}
測(cè)試代碼:
// test
function testThrottle(e, content) {
console.log(e, content);
}
var testThrottleFn = throttle(testThrottle, 1000); // 節(jié)流函數(shù)
document.onmousemove = function (e) {
testThrottleFn(e, 'throttle'); // 給節(jié)流函數(shù)傳參
}
其實(shí)現(xiàn)原理,通過(guò)比對(duì)上一次執(zhí)行時(shí)間與本次執(zhí)行時(shí)間的時(shí)間差與間隔時(shí)間的大小關(guān)系,來(lái)判斷是否執(zhí)行函數(shù)。若時(shí)間差大于間隔時(shí)間,則立刻執(zhí)行一次函數(shù)。并更新上一次執(zhí)行時(shí)間。
- 定時(shí)器實(shí)現(xiàn)函數(shù)節(jié)流
function throttle(fn, delay) {
var timer;
return function () {
var _this = this;
var args = arguments;
if (timer) {
return;
}
timer = setTimeout(function () {
fn.apply(_this, args);
timer = null; // 在delay后執(zhí)行完fn之后清空timer,此時(shí)timer為假,
throttle觸發(fā)可以進(jìn)入計(jì)時(shí)器
}, delay)
}
}
測(cè)試代碼:
function testThrottle(e, content) {
console.log(e, content);
}
var testThrottleFn = throttle(testThrottle, 2000); // 節(jié)流函數(shù)
document.onmousemove = function (e) {
testThrottleFn(e, 'throttle'); // 給節(jié)流函數(shù)傳參
}
上面例子中,如果我們一直在瀏覽器中移動(dòng)鼠標(biāo)(比如10s),則在這10s內(nèi)會(huì)每隔2s執(zhí)行一次testThrottle,這就是函數(shù)節(jié)流。
函數(shù)節(jié)流的目的,是為了限制函數(shù)一段時(shí)間內(nèi)只能執(zhí)行一次。因此,定時(shí)器實(shí)現(xiàn)節(jié)流函數(shù)通過(guò)使用定時(shí)任務(wù),延時(shí)方法執(zhí)行。在延時(shí)的時(shí)間內(nèi),方法若被觸發(fā),則直接退出方法。從而,實(shí)現(xiàn)函數(shù)一段時(shí)間內(nèi)只執(zhí)行一次。
函數(shù)節(jié)流的應(yīng)用場(chǎng)景
需要間隔一定時(shí)間觸發(fā)回調(diào)來(lái)控制函數(shù)調(diào)用頻率:
-
DOM元素的拖拽功能實(shí)現(xiàn)(mousemove) - 搜索聯(lián)想(keyup)
- 計(jì)算鼠標(biāo)移動(dòng)的距離(mousemove)
-
Canvas模擬畫板功能(mousemove) - 滾動(dòng)加載,加載更多或滾到底部監(jiān)聽(tīng)
- 谷歌搜索框,搜索聯(lián)想功能
- 高頻點(diǎn)擊提交,表單重復(fù)提交
函數(shù)防抖
防抖函數(shù):一個(gè)需要頻繁觸發(fā)的函數(shù),在規(guī)定時(shí)間內(nèi),只讓最后一次生效,前面的不生效。
function debounce(fn, delay) {
var timer = null;
return function () {
var _this = this; // 取debounce執(zhí)行作用域的this
var args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
fn.apply(_this, args); // 用apply指向調(diào)用debounce的對(duì)象,
相當(dāng)于_this.fn(args);
}, delay);
};
}
測(cè)試代碼:
function testDebounce(e, content) {
console.log(e, content);
}
var testDebounceFn = debounce(testDebounce, 1000); // 防抖函數(shù)
document.onmousemove = function (e) {
testDebounceFn(e, 'debounce'); // 給防抖函數(shù)傳參
}
上面例子中的debounce就是防抖函數(shù),在document中鼠標(biāo)移動(dòng)的時(shí)候,會(huì)在onmousemove最后觸發(fā)的1s后執(zhí)行回調(diào)函數(shù)testDebounce;如果我們一直在瀏覽器中移動(dòng)鼠標(biāo)(比如10s),會(huì)發(fā)現(xiàn)會(huì)在10+1s后才會(huì)執(zhí)行testDebounce函數(shù)(因?yàn)?code>clearTimeout(timer)),這個(gè)就是函數(shù)防抖。
函數(shù)防抖的應(yīng)用場(chǎng)景
對(duì)于連續(xù)的事件響應(yīng)我們只需要執(zhí)行一次回調(diào):
- 每次 resize/scroll 觸發(fā)統(tǒng)計(jì)事件
- 文本輸入的驗(yàn)證(連續(xù)輸入文字后發(fā)送 AJAX 請(qǐng)求進(jìn)行驗(yàn)證,驗(yàn)證一次就好)
- 搜索框搜索輸入。只需用戶最后一次輸入完,再發(fā)送請(qǐng)求
- 手機(jī)號(hào)、郵箱驗(yàn)證輸入檢測(cè)
- 窗口大小Resize。只需窗口調(diào)整完成后,計(jì)算窗口大小。防止重復(fù)渲染。
總結(jié)
函數(shù)節(jié)流和函數(shù)去抖的核心其實(shí)就是限制某一個(gè)方法被頻繁觸發(fā),其目的都是,降低回調(diào)執(zhí)行頻率,節(jié)省計(jì)算資源,提高瀏覽器的性能。
更多優(yōu)質(zhì)文章可以訪問(wèn)GitHub博客,歡迎帥哥美女前來(lái)Star?。?!