防抖與節流 & 若每個請求必須發送,如何平滑地獲取最後一個接口返回的數據

博客地址:https://ainyi.com/79

日常瀏覽網頁中,在進行窗口的 resize、scroll 或者重複點擊某按鈕發送請求,此時事件處理函數或者接口調用的頻率若無限制,則會加重瀏覽器的負擔,界面可能顯示有誤,服務端也可能出問題,導致用戶體驗非常糟糕
此時可以採用 debounce(防抖)和 throttle(節流)的方式來減少事件或接口的調用頻率,同時又能實現預期效果

防抖:將幾次操作合併爲一此操作進行。原理是維護一個計時器,規定在 delay 時間後觸發函數,但是在 delay 時間內再次觸發的話,就會取消之前的計時器而重新設置。這樣一來,只有最後一次操作能被觸發

節流:使得一定時間內只觸發一次函數。原理是通過判斷是否到達一定時間來觸發函數

區別: 函數節流不管事件觸發有多頻繁,都會保證在規定時間內一定會執行一次真正的事件處理函數,而函數防抖只是在連續觸發的事件後才觸發最後一次事件的函數

上面的解釋,摘抄網上的解答

防抖

debounce:當持續觸發事件時,一定時間段內沒有再觸發事件,事件處理函數纔會執行一次,如果設定的時間到來之前,又一次觸發了事件,就重新開始延時

如下圖,持續觸發 scroll 事件時,並不執行 handle 函數,當 1000ms 內沒有觸發 scroll 事件時,纔會延時觸發 scroll 事件
WechatIMG331.png

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 函數
WechatIMG332.png

時間戳方法

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))

每個請求必須發送的問題

如下圖的購買頁,操作發現一個購買明細的查價接口的頻繁調用問題
如下圖:
WechatIMG329.png

購買頁改變任何一個選項,都會調用查價接口,然後右邊會顯示對應的價格。尤其是購買數量,這是一個數字選擇器,如果用戶頻繁點擊 + 號,就會連續調用多次查價接口,但==最後一次的查價接口返回的數據纔是最後選擇的正確的價格==
每個查價接口逐個請求完畢的時候,==右邊的顯示價格也會逐個改變==,最終變成最後正確的價格,一般來說,這是比較不友好的,用戶點了多次後,不想看到價格在變化,儘管最終是正確的價格,但這個變化的過程是不能接受的

也不應該使用上面的防抖解決方式,不能設置過長的定時器,因爲查價接口不能等太久,也不能設置過短的定時器,否則會出現上面說的問題(價格在變化)

所以這是一個==每個請求必須發送,但是隻顯示最後一個接口返回的數據的問題==

我這裏採用入棧、取棧頂元素比對請求參數的方法解決:

// 查價
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/79

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章