如何理解debounce(防抖)和throttle(節流)?

clipboard.png

前端工程師們都聽過看起來很高級的詞,節流和防抖,其實節流就是throttle,防抖就是debounce,其實這個也屬於前端性能優化的一部分。前端

  • 節流 像閥門同樣控制水流,避免單位時間內流量過大
  • 防抖 防止抖動,比節流的流量控制效果更佳明顯

在作遠程搜索時,若是每輸入1個字就調用1次接口,就會頻繁查詢數據庫,假設咱們的查詢是"12345",不考慮用戶輸入錯誤的狀況,至少會請求5次。vue

再思考一個問題,按鈕的click重複觸發(例如快速點擊2次,3次,...n次)該如何在前端作一層攔截,避免發送重複請求到服務端,最多見的是新增時插入重複數據,這個問題該怎麼辦呢?git

  • 查詢是"12345",至少會請求5次致使頻繁查詢數據庫
  • 有沒有一種方法,能夠隔個幾百毫秒再去查詢呢?(setTimeout)
  • 有沒有更加高級的作法,用戶輸入完成後,停頓了幾百毫秒再去查詢呢?(debounce)
  • 有沒有用戶體驗更加好的作法,不用等待漫長的等待時間從而響應更快呢?(throttle)
  • 快速點擊2次,3次,...n次新增按鈕致使插入重複數據
  • 如何避免用戶單位時間內頻繁點擊按鈕致使重複發送請求的問題?(debounce)
  • 有沒有除了debounce以外的更加精準的方法?(loading)
  • debounce適用場景
  • throttle適用場景
  • debounce和throttle的對比

查詢是"12345",至少會請求5次致使頻繁查詢數據庫

<template>
  <input @input="handleInput"/>
</template>

<script>
export default {
  name: 'input',
  data() {
    return {
      delay: 1000,
      count: 0,
    };
  },
  methods: {
    handleInput(e) {
      console.log(`debounce wait時間爲${this.delay}ms`);
      console.log('觸發了input事件', e.target.value);
      this.count++;
      console.log(`觸發了${this.count}次遠程搜索`);
    },
  },
};
</script>

打印結果:
debounce wait時間爲1000ms
觸發了input事件 1
觸發了1次遠程搜索
debounce wait時間爲1000ms
觸發了input事件 12
觸發了2次遠程搜索
debounce wait時間爲1000ms
觸發了input事件 123
觸發了3次遠程搜索
debounce wait時間爲1000ms
觸發了input事件 1234
觸發了4次遠程搜索
debounce wait時間爲1000ms
觸發了input事件 12345
觸發了5次遠程搜索github

說明:輸入5個數查詢5次,形成了頻繁查詢數據庫的行爲,是一種性能浪費。數據庫

有沒有一種方法,能夠隔個幾百毫秒再去查詢呢?(setTimeout)

有,能夠爲函數設置一個setTimeout函數,至關於定時調用接口,這種方法是低效的,也是很是愚蠢的,須要控制開關定時器,一旦搜索功能多了,就更蠢了。canvas

有沒有更加高級的作法,用戶輸入完成後,停頓了幾百毫秒再去查詢呢?(debounce)

有,debounce(防抖)就是作這個事情的,lodash從0.1.0就支持了這個方法。segmentfault

<template>
  <input @input="debounceHandleInput"/>
</template>

<script>
import _ from 'lodash';

export default {
  name: 'input-debounce',
  data() {
    return {
      delay: 1000,
    };
  },
  computed: {
    debounceHandleInput() {
      return _.debounce(this.handleInput, this.delay);
    },
  },
  methods: {
    handleInput(e) {
      console.log(`debounce wait時間爲${this.delay}ms`);
      console.log('觸發了input事件', e.target.value);
      this.count++;
      console.log(`觸發了${this.count}次遠程搜索`);
    },
  },
};
</script>

打印結果:
debounce wait時間爲1000ms
觸發了input事件 12345後端

說明:在1000ms時間範圍內觸發,僅僅觸發了一次遠程搜索,也就是僅僅調用一次後端接口,達到咱們的預期效果。性能優化

有沒有用戶體驗更加好的作法,不用等待漫長的等待時間從而響應更快呢?(throttle)

<template>
  <input @input="throttleHandleInput"/>
</template>

<script>
import _ from 'lodash';

export default {
  name: 'input-throttle',
  data() {
    return {
      delay: 1000,
      count: 0,
    };
  },
  computed: {
    throttleHandleInput() {
      return _.throttle(this.handleInput, this.delay);
    },
  },
  methods: {
    handleInput(e) {
      console.log(`throttle wait時間爲${this.delay}ms`);
      console.log('觸發了input事件', e.target.value);
      this.count++;
      console.log(`觸發了${this.count}次遠程搜索`);
    },
  },
};
</script>

打印結果:
throttle wait時間爲1000ms
觸發了input事件 1
觸發了1次遠程搜索
throttle wait時間爲1000ms
觸發了input事件 12345微信

說明:在1000ms時間範圍內觸發,僅僅觸發了2次遠程搜索,調用2次後端接口。用戶首次輸入1當即返回數據,保證數據到達速度,也提高了用戶體驗。中間的12,123,1234被節流函數成功攔截避免觸發。而12345是咱們最終須要的搜索結果,在最後返回給用戶。達到咱們的預期效果。

快速點擊2次,3次,...n次新增按鈕致使插入重複數據

<template>
  <button @click="handleClick">新增</button>
</template>

<script>
export default {
  name: 'click',
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    handleClick(e) {
      console.log('觸發了click事件', e.target.value);
      this.count++;
      console.log(`觸發了${this.count}次新增數據`);
    },
  },
};
</script>

觸發了click事件
觸發了1次新增數據
觸發了click事件
觸發了2次新增數據

說明:快速點擊2次「新增」按鈕,而最終只觸發了2次數據新增,形成重複數據插入。

如何避免用戶單位時間內頻繁點擊按鈕致使重複發送請求的問題?(debounce)

<template>
  <button @click="debounceHandleClick">新增</button>
</template>

<script>
import _ from 'lodash';

export default {
  name: 'click-debounce',
  data() {
    return {
      delay: 1000,
      count: 0,
    };
  },
  computed: {
    debounceHandleClick() {
      return _.debounce(this.handleClick, this.delay);
    },
  },
  methods: {
    handleClick(e) {
      console.log(`debounce wait時間爲${this.delay}ms`);
      console.log('觸發了click事件', e.target.value);
      this.count++;
      console.log(`觸發了${this.count}次新增數據`);
    },
  },
};
</script>

打印結果:
debounce wait時間爲1000ms
觸發了click事件
觸發了1次新增數據

說明:快速點擊2次「新增」按鈕,而最終只觸發了1次數據新增,達到了咱們的預期效果。

有沒有除了debounce以外的更加精準的方法?(loading)

loading是指在異步請求完成(成功或者失敗)前,開啓loading,使得按鈕或者用戶界面處於「加載中」「轉圈」「spin"這樣的一個狀態,從而禁止用戶發起重複操做,異步請求完成後,關閉loading。

image.png

<template>
  <Button @click="loadingHandleClick" :loading="loading">新增</Button>
</template>

<script>
export default {
  name: 'click-loading',
  data() {
    return {
      loading: false,
      count: 0,
    };
  },
  methods: {
    loadingHandleClick(e) {
      this.loading = true;
      this.count++;
      console.log(`觸發了${this.count}次新增數據`);
      console.log('發起異步請求,loding爲:', this.loading);
      setTimeout(() => {
        console.log('異步請求執行了1s');
        this.loading = false;
        console.log('異步請求完成,loding爲:', this.loading);
      }, 1000);
    },
  },
};
</script>

觸發了1次新增數據
發起異步請求,loding爲: true
異步請求執行了1s
異步請求完成,loding爲: false

說明:第1次事件觸發後,按鈕處於loading爲true狀態,禁止用戶觸發,1秒後異步請求執行完成,loading爲false,容許用戶再次使用。所以都沒法作到快速點擊2次「新增」按鈕,只能觸發1次,重複的第2次觸發用戶沒法觸發,最終只觸發了1次數據新增,達到了咱們的預期效果。

debounce適用場景

  • Debouncing a click evnet to insert an uniq data
  • Debouncing a input event handler (this example explain this use case)
  • Debouncing a resize event handler

throttle適用場景

  • Throttling a input event handler (this example explain this use case)
  • Throttling a scroll event in infinite scroll(demo case)
  • Throttling a mousemove/touchmove event handler in canvas

debounce和throttle的對比

地址:http://demo.nimius.net/deboun...
圖片:
image

經過在canvas上連續觸發mousemove事件咱們發現:

  • debounce只有當連續事件中止後的一小段時間後再觸發一次,連續事件結束後可能只觸發一次
  • throttle會在連續事件的過程當中,每隔一段時間至少觸發一次,連續事件結束後觸發不止一次

期待和你們交流,共同進步,歡迎你們加入我建立的與前端開發密切相關的技術討論小組:

努力成爲優秀前端工程師!

相關文章
相關標籤/搜索