溫州的網(wǎng)站設計58同城關鍵詞怎么優(yōu)化
參考:JS問題:項目中如何區(qū)分使用防抖或節(jié)流?
面試官:什么是防抖和節(jié)流?有什么區(qū)別?如何實現(xiàn)?
1 為什么要用到防抖和節(jié)流
- 當函數(shù)綁定一些持續(xù)觸發(fā)的事件如:瀏覽器的resize、scroll、keypress、mousemove ,鍵盤輸入,多次快速click等等。
- 如果每次觸發(fā)都要執(zhí)行一次函數(shù),會帶來性能下降,資源請求太頻繁等問題。
- 為了優(yōu)化體驗,需要對這類事件進行調(diào)用次數(shù)的限制,對此我們就可以采用 防抖(debounce) 和 節(jié)流(throttle) 的方式來減少調(diào)用頻率。
防抖(Debounce) 和 節(jié)流(Throttle) 是前端開發(fā)中最常用的優(yōu)化處理方式,本質(zhì)上是 優(yōu)化高頻率執(zhí)行代碼
的一種手段。
2 防抖和節(jié)流兩者定義的區(qū)分
2.1 簡單定義區(qū)分
-
防抖: n 秒后在執(zhí)行該事件,若在 n 秒內(nèi)被重復觸發(fā),則重新計時,即一段時間內(nèi),多次執(zhí)行變?yōu)橹粓?zhí)行
最后一次
。 -
節(jié)流: n 秒內(nèi)只運行一次,若在 n 秒內(nèi)重復觸發(fā),只有一次生效,即一段時間內(nèi),多次執(zhí)行變?yōu)橹粓?zhí)行
第一次
。
2.2 詳細定義區(qū)分
- 防抖(debounce):是指在一段時間內(nèi)多次觸發(fā)相同事件時,只執(zhí)行最后一次觸發(fā)的事件。這意味著,在一系列觸發(fā)事件中,如果在指定的時間間隔內(nèi),發(fā)生了新的觸發(fā)事件,那么前面的觸發(fā)事件將被忽略,只有最后一次觸發(fā)事件會被執(zhí)行。
- 節(jié)流(throttle):是指在一段時間內(nèi)多次觸發(fā)相同事件時,只執(zhí)行一次事件。這意味著,無論觸發(fā)事件發(fā)生多少次,在指定的時間間隔內(nèi)只會執(zhí)行第一次事件。
一個經(jīng)典的比喻:
想象每天上班大廈底下的電梯。把電梯完成一次運送,類比為一次函數(shù)的執(zhí)行和響應。
假設電梯有兩種運行策略 debounce 和 throttle,超時設定為15秒,不考慮容量限制。
電梯第一個人進來后,15秒后準時運送一次,這是節(jié)流。
電梯第一個人進來后,等待15秒。如果過程中又有人進來,15秒等待重新計時,直到15秒后開始運送,這是防抖。
3 防抖和節(jié)流兩者應用場景的區(qū)分
真實的項目中,在不同的場景下靈活切換使用防抖或節(jié)流,才會更加減少不必要的資源消耗,更加提高前端應用的性能和響應性。
3.1 防抖(Debounce)的應用場景
- 搜索框輸入: 當用戶在搜索框中輸入內(nèi)容時,可以使用防抖來延遲觸發(fā)搜索請求。只有在用戶停止輸入一段時間后才會發(fā)送請求,避免頻繁的請求發(fā)送。
- 窗口調(diào)整resize: 當窗口大小調(diào)整時,可以使用防抖來優(yōu)化執(zhí)行某些操作的頻率,例如重新計算布局或重新渲染頁面。
- 按鈕點擊: 當用戶點擊一個按鈕時,可以使用防抖來確保只有在用戶停止點擊一段時間后才會執(zhí)行相應的操作,避免誤操作或重復執(zhí)行。
- 手機號、郵箱驗證輸入檢測。
3.2 節(jié)流(Throttle)的應用場景
- 頁面滾動: 當用戶滾動頁面時,可以使用節(jié)流來限制觸發(fā)滾動事件的頻率。例如,在滾動過程中只執(zhí)行某些操作的固定時間間隔,以減少事件處理的次數(shù)。
- 鼠標移動: 當用戶移動鼠標時,可以使用節(jié)流來控制觸發(fā)鼠標移動事件的頻率。例如,在一定時間內(nèi)只執(zhí)行一次鼠標移動的處理邏輯,避免過多的計算和渲染操作。
- 實時通信: 在實時通信應用中,如聊天應用或在線協(xié)作工具,可以使用節(jié)流來限制發(fā)送消息的頻率,以避免發(fā)送過多的請求或數(shù)據(jù)。
- 高頻點擊提交,表單重復提交。
4 防抖
Vue2:
/*** @param {Function} func* @param {number} wait* @param {boolean} immediate* @return {*}*/
export function debounce(func, wait, immediate) {let timeout, args, context, timestamp, resultconst later = function() {// 據(jù)上一次觸發(fā)時間間隔const last = +new Date() - timestamp// 上次被包裝函數(shù)被調(diào)用時間間隔 last 小于設定時間間隔 waitif (last < wait && last > 0) {timeout = setTimeout(later, wait - last)} else {timeout = null// 如果設定為immediate===true,因為開始邊界已經(jīng)調(diào)用過了此處無需調(diào)用if (!immediate) {result = func.apply(context, args)if (!timeout) context = args = null}}}return function(...args) { // 返回一個包裝后的函數(shù)context = this // 保存函數(shù)執(zhí)行上下文對象timestamp = +new Date()const callNow = immediate && !timeout // 是否立即調(diào)用函數(shù)的條件// 如果延時不存在,重新設定延時if (!timeout) timeout = setTimeout(later, wait)if (callNow) { // 如果滿足立即調(diào)用條件,則立即執(zhí)行函數(shù)result = func.apply(context, args)context = args = null}return result}
}
Vue3:
export const useDebounce = (func: Function, delay: number, immediate: boolean = false) => {// let timer: ReturnType<typeof setTimeout>;let timer: NodeJS.Timeout | null = null;return (...args: any[]) => {if(timer) clearTimeout(timer);timer = setTimeout(() => {func(...args);}, delay)}}
手寫防抖函數(shù):
// 手寫防抖函數(shù)/*** 執(zhí)行頻率:在事件停止觸發(fā)的延遲時間內(nèi)執(zhí)行一次* * 防抖函數(shù)應用場景:* 1、頻繁的點擊按鈕,觸發(fā)某個事件* 2、監(jiān)聽瀏覽器滾動事件,完成某些特定操作* 3、用戶縮放瀏覽器的resize事件等等* * 目的:延遲執(zhí)行,確保只在停止操作后執(zhí)行一次* *//*** @param {function} func - 目標對象* @param {number} wait - 防抖間隔時間* @param {boolean} immediate - 開啟初次立即執(zhí)行* @param {function} resultCallback - 目標對象返回值的回調(diào)函數(shù)* @return {function} - 防抖函數(shù)* @author m* @example** ```js* const inputChange = function (event) {* console.log(`發(fā)送了第${++counter}次網(wǎng)絡請求`, this, event)* // 返回值* return "test"* }* * const debounceChange = debounce(inputChange, 2000, false, (res) => {* console.log("拿到真正執(zhí)行函數(shù)的返回值:", res)* })* * // 取消功能* const cancelBtn = document.querySelector("#cancel") // 獲取取消按鈕* cancelBtn.onclick = function() {* debounceChange.cancel()* }* ```* */function debounce(func, wait = 200, immediate = false, resultCallback) {// 保存每次定時器操作,在下一次規(guī)定時間內(nèi) 重復調(diào)用提供取消信息let timer = null;// 作為判斷當前為立即執(zhí)行 or 防抖模式let isInvoke = false;const _debounce = function (...args) {// 排除第一次調(diào)用無timer,其余規(guī)定時刻清除上一次定時器if (timer) clearTimeout(timer)if (immediate && !isInvoke) {const result = func.apply(this, args);// 將目標對象返回值返回后,能夠進行回調(diào)執(zhí)行處理if (resultCallback) resultCallback(result);// 初次立即執(zhí)行,后續(xù)正常防抖模式isInvoke = true;} else {// 定時,正常防抖模式內(nèi)容timer = setTimeout(() => {const result = func.apply(this, args) // 綁定this為元素本身以及event事件// 將目標對象返回值返回后,能夠進行回調(diào)執(zhí)行處理if (resultCallback) resultCallback(result);// 防抖模式結束,下次輸入內(nèi)容立即執(zhí)行isInvoke = false;}, wait)}}// 封裝取消功能_debounce.cancel = function () {if (timer) clearTimeout(timer);// 初始化timer = null;isInvoke = false;}return _debounce;
}
5 節(jié)流
Vue2:
// 節(jié)流函數(shù)
export function throttle(func, wait) {let timeout; // 定義一個計時器變量,用于限制函數(shù)調(diào)用頻率return function (...args) { // 返回一個包裝后的函數(shù)const context = this; // 保存函數(shù)執(zhí)行上下文對象if (!timeout) { // 如果計時器不存在func.apply(context, args); // 執(zhí)行函數(shù)timeout = setTimeout(() => {timeout = null; // 清空計時器變量}, wait); // 創(chuàng)建計時器,在指定時間后重置計時器變量}};
手寫節(jié)流函數(shù):
// 手寫節(jié)流函數(shù)/*** 執(zhí)行頻率:在事件頻繁觸發(fā)時,以固定的時間間隔執(zhí)行* * 應用場景:頁面滾動、鼠標移動、窗口調(diào)整大小等高頻事件* * 目的:限制執(zhí)行頻率,降低函數(shù)調(diào)用的次數(shù)*//*** 創(chuàng)建一個節(jié)流函數(shù),在給定的時間間隔內(nèi)最多執(zhí)行一次目標函數(shù)* @param {function} func - 需要節(jié)流的目標函數(shù)* @param {number} intervalTime - 節(jié)流的間隔時間(毫秒)* @param {Object} [options] - 節(jié)流的配置選項* @param {boolean} [options.leading = true] - 是否在第一次調(diào)用時立即執(zhí)行* @param {boolean} [options.trailing = false] - 是否在冷卻時間結束后追加一次執(zhí)行* @param {function} [options.resultCallback] - 每次節(jié)流函數(shù)執(zhí)行后的回調(diào)函數(shù),用于處理目標函數(shù)的返回值* @return {function} - 返回包裝后的節(jié)流函數(shù) * @author m* @example** ```js* // 創(chuàng)建一個節(jié)流函數(shù),每 1000 毫秒最多執(zhí)行一次* const throttledFn = throttle(() => {* console.log('Throttled function executed');* }, 1000);* * // 綁定事件* document.addEventListener('scroll', throttledFn);* * // 調(diào)用 cancel 方法* throttledFn.cancel();* ```* */function throttle(func, intervalTime = 200, options = { leading: true, trailing: false, resultCallback }) {// 記錄上一次的開始時間const { leading, trailing, resultCallback } = options;// 每次點擊時,重置當前最新時間let lastTime = 0;// 記錄let timer = null;const _throttle = function (...args) {// 獲取"第一次"調(diào)用時間let nowTime = new Date().getTime();// 必須第一次執(zhí)行(lastTime) 且 用戶傳參調(diào)節(jié)為首次不立即執(zhí)行(leading)if (!lastTime && !leading) lastTime = nowTime;// 獲取冷卻時間 = 節(jié)流間隔時間 - 第一次響應時間 + 發(fā)起響應時間const remainTime = intervalTime - nowTime + lastTime;// 達到冷卻,可以執(zhí)行響應函數(shù)if (remainTime <= 0) {// 如果節(jié)流間隔后存在正常響應函數(shù),將"最后一次響應函數(shù)"的定時器清除if (timer) {clearTimeout(timer);timer = null;}const result = func.apply(this, args);// 返回值if (resultCallback) resultCallback(result);// 記錄函數(shù)上一次被成功調(diào)用的時間戳lastTime = nowTime;return;}// 節(jié)流最后一次也可以執(zhí)行的判斷邏輯if (trailing && !timer) {// 返回timer,只有冷卻歸零后還未存在正常調(diào)用,timer尚未清零才能調(diào)用,一旦調(diào)用則重置timertimer = setTimeout(() => {timer = setTimeout(() => {timer = null;lastTime = !leading ? 0 : new Date().getTime();const result = func.apply(this, args);// 返回值if (resultCallback) resultCallback(result);})}, remainTime);}}// 取消方法_throttle.cancel = function () {// 有值的情況才進行取消if (timer) clearTimeout(timer);// 初始化timer = null;lastTime = 0;}return _throttle;
}
參考:JS高級-手寫防抖節(jié)流函數(shù)與實現(xiàn)事件總線