1. 程式人生 > >防抖與節流 & 若每個請求必須傳送,如何平滑地獲取最後一個介面返回的資料

防抖與節流 & 若每個請求必須傳送,如何平滑地獲取最後一個介面返回的資料

部落格地址: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