防抖與節流 & 若每個請求必須傳送,如何平滑地獲取最後一個介面返回的資料
部落格地址:https://ainyi.com/79
日常瀏覽網頁中,在進行視窗的 resize、scroll 或者重複點選某按鈕傳送請求,此時事件處理函式或者介面呼叫的頻率若無限制,則會加重瀏覽器的負擔,介面可能顯示有誤,服務端也可能出問題,導致使用者體驗非常糟糕
此時可以採用 debounce(防抖)和 throttle(節流)的方式來減少事件或介面的呼叫頻率,同時又能實現預期效果
防抖:將幾次操作合併為一此操作進行。原理是維護一個計時器,規定在 delay 時間後觸發函式,但是在 delay 時間內再次觸發的話,就會取消之前的計時器而重新設定。這樣一來,只有最後一次操作能被觸發
節流:使得一定時間內只觸發一次函式。原理是通過判斷是否到達一定時間來觸發函式
區別: 函式節流不管事件觸發有多頻繁,都會保證在規定時間內一定會執行一次真正的事件處理函式,而函式防抖只是在連續觸發的事件後才觸發最後一次事件的函式
上面的解釋,摘抄網上的解答
防抖
debounce:當持續觸發事件時,一定時間段內沒有再觸發事件,事件處理函式才會執行一次,如果設定的時間到來之前,又一次觸發了事件,就重新開始延時
如下圖,持續觸發 scroll 事件時,並不執行 handle 函式,當 1000ms 內沒有觸發 scroll 事件時,才會延時觸發 scroll 事件
function debounce(fn, wait) { let timeout = null return function() { if(timeout !== null) { clearTimeout(timeout) } timeout = setTimeout(fn, wait) } } // 處理函式 function handle() { console.log('處理函式', Math.random()) } // 滾動事件 window.addEventListener('scroll', debounce(handle, 1000))
節流
throttle:當持續觸發事件時,保證一定時間段內只調用一次事件處理函式
仔細瞭解了才知道,我以前剛學前端的時候,做 banner 圖特效,兩邊的點選按鈕如果一直重複點選就會出問題,後面摸索了此方法,原來這名字叫做節流
如下圖,持續觸發 scroll 事件時,並不立即執行 handle 函式,每隔 1000 毫秒才會執行一次 handle 函式
時間戳方法
let throttle = function(func, delay) { let prev = Date.now() return function() { let context = this let args = arguments let now = Date.now() if(now - prev >= delay) { func.apply(context, args) prev = Date.now() } } } function handle() { console.log(Math.random()) } window.addEventListener('scroll', throttle(handle, 1000))
定時器方法
let throttle = function(func, delay) {
let timer = null
return function() {
let context = this
let args = arguments
if(!timer) {
timer = setTimeout(function() {
func.apply(context, args)
timer = null
}, delay)
}
}
}
function handle() {
console.log(Math.random())
}
window.addEventListener('scroll', throttle(handle, 1000))
時間戳+定時器
let throttle = function(func, delay) {
let timer = null
let startTime = Date.now()
return function() {
let curTime = Date.now()
let remaining = delay - (curTime - startTime)
let context = this
let args = arguments
clearTimeout(timer)
if(remaining <= 0) {
func.apply(context, args)
startTime = Date.now()
} else {
timer = setTimeout(func, remaining)
}
}
}
function handle() {
console.log(Math.random())
}
window.addEventListener('scroll', throttle(handle, 1000))
每個請求必須傳送的問題
如下圖的購買頁,操作發現一個購買明細的查價介面的頻繁呼叫問題
如下圖:
購買頁改變任何一個選項,都會呼叫查價介面,然後右邊會顯示對應的價格。尤其是購買數量,這是一個數字選擇器,如果使用者頻繁點選 + 號,就會連續呼叫多次查價介面,但==最後一次的查價介面返回的資料才是最後選擇的正確的價格==
每個查價介面逐個請求完畢的時候,==右邊的顯示價格也會逐個改變==,最終變成最後正確的價格,一般來說,這是比較不友好的,使用者點了多次後,不想看到價格在變化,儘管最終是正確的價格,但這個變化的過程是不能接受的
也不應該使用上面的防抖解決方式,不能設定過長的定時器,因為查價介面不能等太久,也不能設定過短的定時器,否則會出現上面說的問題(價格在變化)
所以這是一個==每個請求必須傳送,但是隻顯示最後一個介面返回的資料的問題==
我這裡採用入棧、取棧頂元素比對請求引數的方法解決:
// 查價
async getPrice() {
// 請求引數
const reqData = this.handleData()
// push 入棧
this.priceStack.push(reqData)
const { result } = await getProductPrice(reqData)
// 核心程式碼,取棧頂元素(最後請求的引數)比對
if(this.$lang.isEqual(this.$array.last(this.priceStack), reqData)) {
// TODO
// 展示價格程式碼...
}
}
註解,上述的 this.$lang.isEqual、this.$array.last 均是 lodash 外掛提供的方法
註冊到 Vue 中
import array from 'lodash/array'
import Lang from 'lodash/lang'
Vue.prototype.$array = array
Vue.prototype.$lang = Lang
部落格地址:https://ainyi.com