淺談JavaScript節(jié)流和防抖函數(shù)
概念
節(jié)流函數(shù)
間隔固定的時(shí)間執(zhí)行傳入的方法
目的是防止函數(shù)執(zhí)行的頻率過快,影響性能.常見于跟滾動(dòng),鼠標(biāo)移動(dòng)事件綁定的功能.
防抖函數(shù)
對(duì)于接觸過硬件的人也許更好理解,硬件按鈕按下時(shí),由于用戶按住時(shí)間的長短不一,會(huì)多次觸發(fā)電流的波動(dòng),加一個(gè)防抖函數(shù)就會(huì)只觸發(fā)一次,防止了無意義的電流波動(dòng)引起的問題.
按鍵防反跳(Debounce)為什么要去抖動(dòng)呢?機(jī)械按鍵在按下時(shí),并非按下就接觸的很好,尤其是有簧片的機(jī)械開關(guān),會(huì)在接觸的瞬間反復(fù)的開合多次,直到開關(guān)狀態(tài)完全改變。
應(yīng)用在前端時(shí),常見的場(chǎng)景是,輸入框打字動(dòng)作結(jié)束一段時(shí)間后再去觸發(fā)查詢/搜索/校驗(yàn),而不是每打一個(gè)字都要去觸發(fā),造成無意義的ajax查詢等,或者與調(diào)整窗口大小綁定的函數(shù),其實(shí)只需要在最后窗口大小固定之后再去執(zhí)行動(dòng)作.
自己的實(shí)現(xiàn)
防抖函數(shù)
關(guān)鍵點(diǎn)在于每次觸發(fā)時(shí)都清空延時(shí)函數(shù)的手柄,只有最后一次觸發(fā)不會(huì)清空手柄,所以最后一次觸發(fā)會(huì)等默認(rèn)的1s后去執(zhí)行debounce傳入的參數(shù)函數(shù)f. debounce內(nèi)部返回的閉包函數(shù),是真正每次被調(diào)用觸發(fā)的函數(shù),不再是原本的f,所以這里的arguments取閉包函數(shù)環(huán)境變量中的arguments并在執(zhí)行f時(shí)傳給f,在setTimeout函數(shù)的外面取得.
let debounce = function(f, interval = 1000) {
let handler = null;
return function() {
if (handler) {
clearTimeout(handler);
}
let arg = arguments;
handler = setTimeout(function() {
f.apply(this, arg);
clearTimeout(handler);
}, interval)
}
}
應(yīng)用:
let input = document.querySelector('#input');
input.addEventListener('input', debounce(function(e) {
console.log("您的輸入是",e.target.value)
}))
更高級(jí)的實(shí)現(xiàn)還會(huì)考慮到,以leading和trailing作為參數(shù),起始先執(zhí)行一次函數(shù)并消除后面的抖動(dòng),還是最后執(zhí)行一下函數(shù),消除前面的抖動(dòng),如同我這里的例子.后面分析loadash的防抖函數(shù)時(shí)會(huì)詳細(xì)解析.
節(jié)流函數(shù)
let throttle = function(f,gap = 300){
let lastCall = 0;
return function(){
let now = Date.now();
let ellapsed = now - lastCall;
if(ellapsed < gap){
return
}
f.apply(this,arguments);
lastCall = Date.now();
}
}
閉包函數(shù)在不斷被調(diào)用的期間,去記錄離上一次調(diào)用間隔的時(shí)間,如果間隔時(shí)間小于節(jié)流設(shè)置的時(shí)間則直接返回,不去執(zhí)行真正被包裹的函數(shù)f.只有間隔時(shí)間大于了節(jié)流函數(shù)設(shè)置的時(shí)間gap,才調(diào)用f,并更新調(diào)用時(shí)間.
應(yīng)用:
document.addEventListener('scroll', throttle(function (e) {
// 判斷是否滾動(dòng)到底部的邏輯
console.log(e,document.documentElement.scrollTop);
}));
lodash源碼分析
以上是對(duì)節(jié)流防抖函數(shù)最基礎(chǔ)簡單的實(shí)現(xiàn),我們接下來分析一下lodash庫中節(jié)流防抖函數(shù)的分析.
節(jié)流函數(shù)的使用
$(window).on('scroll', _.debounce(doSomething, 200));
function debounce(func, wait, options) {
var lastArgs,
lastThis,
result,
timerId,
lastCallTime = 0,
lastInvokeTime = 0,
leading = false,
maxWait = false,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
wait = wait || 0;
if (isObject(options)) {
leading = !!options.leading;
maxWait = 'maxWait' in options && Math.max((options.maxWait) || 0, wait);
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
function invokeFunc(time) {
var args = lastArgs,
thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
console.log("leadingEdge setTimeout")
// Reset any `maxWait` timer.
lastInvokeTime = time;
// Start the timer for the trailing edge.
timerId = setTimeout(timerExpired, wait);
// Invoke the leading edge.
return leading ? invokeFunc(time) : result;
}
function remainingWait(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime,
result = wait - timeSinceLastCall;
console.log("remainingWait",result)
return maxWait === false ? result : Math.min(result, maxWait - timeSinceLastInvoke);
}
function shouldInvoke(time) {
console.log("shouldInvoke")
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime;
console.log("time",time,"lastCallTime",lastCallTime,"timeSinceLastCall",timeSinceLastCall)
console.log("time",time,"lastInvokeTime",lastInvokeTime,"timeSinceLastInvoke",timeSinceLastInvoke)
console.log("should?",(!lastCallTime || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxWait !== false && timeSinceLastInvoke >= maxWait)))
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (!lastCallTime || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxWait !== false && timeSinceLastInvoke >= maxWait));
}
function timerExpired() {
console.log("timerExpired")
var time = Date.now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
console.log("Restart the timer.",time,remainingWait(time))
// Restart the timer.
console.log("timerExpired setTimeout")
timerId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
clearTimeout(timerId);
timerId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
console.log("trailing",trailing,"lastArgs",lastArgs)
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId);
}
lastCallTime = lastInvokeTime = 0;
lastArgs = lastThis = timerId = undefined;
}
function flush() {
return timerId === undefined ? result : trailingEdge(Date.now());
}
function debounced() {
var time = Date.now(),
isInvoking = shouldInvoke(time);
console.log("time",time);
console.log("isInvoking",isInvoking);
lastArgs = arguments;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
// Handle invocations in a tight loop.
clearTimeout(timerId);
console.log("setTimeout")
timerId = setTimeout(timerExpired, wait);
return invokeFunc(lastCallTime);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
}
ref
https://css-tricks.com/debouncing-throttling-explained-examples/
https://github.com/lodash/lodash/blob/4.7.0/lodash.js#L9840
https://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/
以上就是淺談JavaScript節(jié)流和防抖函數(shù)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript節(jié)流和防抖函數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
document.selection.createRange方法與實(shí)例
document.selection.createRange() 根據(jù)當(dāng)前文字選擇返回 TextRange 對(duì)象,或根據(jù)控件選擇返回 ControlRange 對(duì)象2006-10-10
基于JS實(shí)現(xiàn)禁止查看源碼及獲取鍵盤的按鍵值
這篇文章主要介紹了基于JS實(shí)現(xiàn)禁止查看源碼及獲取鍵盤的按鍵值,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02
JS使用正則表達(dá)式驗(yàn)證身份證號(hào)碼
這篇文章主要介紹了JS使用正則表達(dá)式驗(yàn)證身份證號(hào)碼的相關(guān)資料,需要的朋友可以參考下2017-06-06
一個(gè)html5播放視頻的video控件只支持android的默認(rèn)格式mp4和3gp
寫了個(gè)html5播放視頻的video控件,只支持mp4和3gp(android和ios默認(rèn)支持的格式就寫了這個(gè)) ,需要的朋友可以參考下2014-05-05
js 實(shí)現(xiàn)獲取name 相同的頁面元素并循環(huán)遍歷的方法
下面小編就為大家?guī)硪黄猨s 實(shí)現(xiàn)獲取name 相同的頁面元素并循環(huán)遍歷的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
javaScript 刪除確認(rèn)實(shí)現(xiàn)方法小結(jié)
因?yàn)閷?duì)于內(nèi)容的刪除是件很重要的事,所以一般的系統(tǒng)中,都需要?jiǎng)h除確認(rèn)一下,以免誤刪,具體的方法如下,大家可以參考下。2009-12-12

