Javascript節(jié)流函數(shù)throttle和防抖函數(shù)debounce
問題的引出
在一些場景往往由于事件頻繁被觸發(fā),因而頻繁地進(jìn)行DOM操作、資源加載,導(dǎo)致UI停頓甚至瀏覽器崩潰。
在這樣的情況下,我們實(shí)際上的需求大多為停止改變大小n毫秒后執(zhí)行后續(xù)處理;而其他事件大多的需求是以一定的頻率執(zhí)行后續(xù)處理。針對這兩種需求就出現(xiàn)了debounce和throttle兩種解決辦法。
1. resize事件
2. mousemove事件
3. touchmove事件
4.scroll事件
throttle 與 debounce
在現(xiàn)在很多的javascript框架中都提供了這兩個函數(shù)。例如 jquery中有throttle和debounce插件, underscore.js ,Lodash.js 等都提供了這兩個函數(shù)。
原理:
首先我們會想到設(shè)置一定的時(shí)間范圍delay,每隔delayms 執(zhí)行不超過一次。
事件處理函數(shù)什么時(shí)候執(zhí)行能? 這里有兩個選擇,一是先執(zhí)行,再間隔delayms來等待;或者是先等待delayms,然后執(zhí)行事件處理函數(shù)。
操作過程中的事件全不管,反正只執(zhí)行一次事件處理。
相同低,這一次的事件處理可以是先執(zhí)行一次,然后后面的事件都不管; 或者前面的都不管,最后操作完了再執(zhí)行一次事件處理。
區(qū)別:
1. throttle
如果將水龍頭擰緊直到水是以水滴的形式流出,那你會發(fā)現(xiàn)每隔一段時(shí)間,就會有一滴水流出。
也就是會說預(yù)先設(shè)定一個執(zhí)行周期,當(dāng)調(diào)用動作的時(shí)刻大于等于執(zhí)行周期則執(zhí)行該動作,然后進(jìn)入下一個新周期。
2.debounce
如果用手指一直按住一個彈簧,它將不會彈起直到你松手為止。
也就是說當(dāng)調(diào)用動作n毫秒后,才會執(zhí)行該動作,若在這n毫秒內(nèi)又調(diào)用此動作則將重新計(jì)算執(zhí)行時(shí)間。
簡單代碼實(shí)現(xiàn)及實(shí)驗(yàn)結(jié)果
那么下面我們自己簡單地實(shí)現(xiàn)下這兩個函數(shù):
throttle 函數(shù):
window.addEventListener("resize", throttle(callback, 300, {leading:false})); window.addEventListener("resize", callback2); function callback () { console.count("Throttled"); } function callback2 () { console.count("Not Throttled"); } /** * 頻率控制函數(shù), fn執(zhí)行次數(shù)不超過 1 次/delay * @param fn{Function} 傳入的函數(shù) * @param delay{Number} 時(shí)間間隔 * @param options{Object} 如果想忽略開始邊界上的調(diào)用則傳入 {leading:false}, * 如果想忽略結(jié)束邊界上的調(diào)用則傳入 {trailing:false}, * @returns {Function} 返回調(diào)用函數(shù) */ function throttle(fn,delay,options) { var wait=false; if (!options) options = {}; return function(){ var that = this,args=arguments; if(!wait){ if (!(options.leading === false)){ fn.apply(that,args); } wait=true; setTimeout(function () { if (!(options.trailing === false)){ fn.apply(that,args); } wait=false; },delay); } } }
將以上代碼貼入瀏覽器中運(yùn)行,可得到:
下面再看debounce函數(shù)的情況,
debounce 函數(shù):
window.addEventListener("resize", throttle(callback, 300, {leading:false})); window.addEventListener("resize", callback2); function callback () { console.count("Throttled"); } function callback2 () { console.count("Not Throttled"); } /** * 空閑控制函數(shù), fn僅執(zhí)行一次 * @param fn{Function} 傳入的函數(shù) * @param delay{Number} 時(shí)間間隔 * @param options{Object} 如果想忽略開始邊界上的調(diào)用則傳入 {leading:false}, * 如果想忽略結(jié)束邊界上的調(diào)用則傳入 {trailing:false}, * @returns {Function} 返回調(diào)用函數(shù) */ function debounce(fn, delay, options) { var timeoutId; if (!options) options = {}; var leadingExc = false; return function() { var that = this, args = arguments; if (!leadingExc&&!(options.leading === false)) { fn.apply(that, args); } leadingExc=true; if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(function() { if (!(options.trailing === false)) { fn.apply(that, args); } leadingExc=false; }, delay); } }
將以上代碼貼入瀏覽器中運(yùn)行,分三次改變窗口大小,可看到,每一次改變窗口的大小都會把開始和結(jié)束邊界的事件處理函數(shù)各執(zhí)行一次:
如果是一次性改變窗口大小,會發(fā)現(xiàn)開始和結(jié)束的邊界各執(zhí)行一次時(shí)間處理函數(shù),請注意與一次性改變窗口大小時(shí) throttle 情況的對比:
underscore.js 的代碼實(shí)現(xiàn)
_.throttle函數(shù)
/** * 頻率控制函數(shù), fn執(zhí)行次數(shù)不超過 1 次/delay * @param fn{Function} 傳入的函數(shù) * @param delay{Number} 時(shí)間間隔 * @param options{Object} 如果想忽略開始邊界上的調(diào)用則傳入 {leading:false}, * 如果想忽略結(jié)束邊界上的調(diào)用則傳入 {trailing:false}, * @returns {Function} 返回調(diào)用函數(shù) */ _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; 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; }; return 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) { 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; }; };
_.debounce函數(shù)
/** * 空閑控制函數(shù), fn僅執(zhí)行一次 * @param fn{Function} 傳入的函數(shù) * @param delay{Number} 時(shí)間間隔 * @param options{Object} 如果想忽略開始邊界上的調(diào)用則傳入 {leading:false}, * 如果想忽略結(jié)束邊界上的調(diào)用則傳入 {trailing:false}, * @returns {Function} 返回調(diào)用函數(shù) */ _.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { var last = _.now() - timestamp; if (last < wait && last > 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; };
參考的文章
Debounce and Throttle: a visual explanation
jQuery throttle / debounce: Sometimes, less is more!
underscore.js
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Javascript實(shí)現(xiàn)滑塊滑動改變值的實(shí)現(xiàn)代碼
一個功能,值得一說的是本頁面的滑塊實(shí)現(xiàn)由于對美工不是很熟悉所以上圖了,感興趣的朋友可以了解下哈2013-04-04Typescript井字棋的項(xiàng)目實(shí)現(xiàn)
本文主要介紹了Typescript井字棋的項(xiàng)目實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08JS實(shí)現(xiàn)頁面滾動到關(guān)閉時(shí)的位置與不滾動效果
這篇文章主要介紹了JS實(shí)現(xiàn)頁面滾動到關(guān)閉時(shí)的位置與不滾動效果,滾動有兩種方案,其一,利用路由中的meta,在離開頁面時(shí)緩存 top 信息,其二,利用keep-alive緩存整個頁面。但是僅限于沒有實(shí)時(shí)數(shù)據(jù)變動的頁面,需要的朋友可以參考下本文2022-06-06JS 根據(jù)子網(wǎng)掩碼,網(wǎng)關(guān)計(jì)算出所有IP地址范圍示例
這篇文章主要介紹了JS 根據(jù)子網(wǎng)掩碼,網(wǎng)關(guān)計(jì)算出所有IP地址范圍,涉及IP地址、子網(wǎng)的正則驗(yàn)證,子網(wǎng)掩碼計(jì)算等相關(guān)操作技巧,需要的朋友可以參考下2016-09-09js操作table元素實(shí)現(xiàn)表格行列新增、刪除技巧總結(jié)
這篇文章主要介紹了js操作table元素實(shí)現(xiàn)表格行列新增、刪除技巧,以實(shí)例形式分析總結(jié)了JavaScript針對table表格進(jìn)行行列的增加與刪除相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11JavaScript canvas實(shí)現(xiàn)七彩時(shí)鐘效果
這篇文章主要為大家詳細(xì)介紹了JavaScript canvas實(shí)現(xiàn)七彩時(shí)鐘效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-051秒50萬字!js實(shí)現(xiàn)關(guān)鍵詞匹配
1秒50萬字!js實(shí)現(xiàn)關(guān)鍵詞匹配,快速進(jìn)行關(guān)鍵字匹配,感興趣的小伙伴們可以參考一下2016-08-08JavaScript通過function定義對象并給對象添加toString()方法實(shí)例分析
這篇文章主要介紹了JavaScript通過function定義對象并給對象添加toString()方法,實(shí)例分析了javascript中function定義對象及添加方法的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03js常用函數(shù)push()、pop()、shift()、unshift()、slice()和splice()等詳解
這篇文章主要給大家介紹了關(guān)于js常用函數(shù)push()、pop()、shift()、unshift()、slice()和splice()等的相關(guān)資料,js中數(shù)組操作函數(shù)還是非常多的,今天忽然想到來總結(jié)一下,也算是溫故而知新吧,需要的朋友可以參考下2023-11-11