「防抖與節流 」每個請求必須發送,平滑地獲取最後一個返回值

博客地址: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
「防抖與節流 」每個請求必須發送,平滑地獲取最後一個返回值


分享到:


相關文章: