[JS性能優化]函數去抖(debounce)與函數節流(throttle)

前言

這是個老生常談的話題了,之因此還搬出來說講,緣由之一是以前根本就沒在乎,近期面臨的一些問題須要用到這兩個小技巧;緣由之二,這兩個技巧帶來的優化不小;緣由之三,順便複習一下閉包。html

開發中你可能會遇到下面的狀況:react

  • 監聽Window對象的resizescroll事件
  • 拖拽時監聽mousemove
  • 文字輸入時,對輸入字符串進行處理,好比要把markdwon轉換成html
  • 監聽文件變化,重啓服務

第一種和第三種狀況,事件短期內被頻繁出發,若是在事件中有大量的計算,頻繁操做DOM,資源加載等重行爲,可能會致使UI卡頓,嚴重點甚至讓瀏覽器掛掉。對於第四種狀況,有的開發者保存編輯好的文件喜歡按屢次Ctrl+S,如果快速的重啓服務還能Hold住,可是要是重啓一個應用,就可能屢次沒必要要的重啓。npm

針對上面這一系列的需求,因而有了debouncethrottle兩種解決辦法。瀏覽器

函數節流

函數按照一個週期執行,例如給window綁定一個resize事件以後,只要窗口改變大小改變就打印1,若是不採用函數節流,當咱們將窗口調節的時候發現控制檯一直打印1,可是使用了函數節流後咱們會發現調節的過程當中,每隔一段時間纔打印1閉包

一個函數節流的簡單實現:app

/** * * @param func {Function} 實際要執行的函數 * @param wait {Number} 執行間隔,單位是毫秒(ms),默認100ms * * @return {Function} 返回一個「節流」函數 */

function throttle(func, wait = 100) {
   // 利用閉包保存定時器和上次執行時間
   let timer = null;
   let previous; // 上次執行時間
   return function() {
       // 保存函數調用時的上下文和參數,傳遞給 fn
       const context = this;
       const args = arguments;
       const now = +new Date();
       if (previous && now < previous + wait) { // 週期之中
           clearTimeout(timer);
   	    timer = setTimeout(function() {
   	        previous = now;
   	        func.apply(context, args);
   	    }, wait);
       } else {
           previous = now;
           func.apply(context, args);
       }
   };
}
複製代碼

使用的方法也很簡單:electron

const btn = document.getElementById('btn');

function demo() {
   console.log('click');
}
btn.addEventListener('click', throttle(demo, 1000));
複製代碼

看看React中怎麼使用的,下面監聽窗口的resize和輸入框的onChange事件:函數

import React, { Component } from 'react';
import { throttle } from '../../utils/utils';

export default class Demo extends Component {
 constructor() {
   super();
   this.change = throttle((e) => {
       console.log(e.target.value);
       console.log('throttle');
   }, 100);
 }
 
 componentDidMount() {
   window.addEventListener('resize', throttle(this.onWindowResize, 60));
 }
 
  componentWillUnmount() {
   window.removeEventListener('resize', throttle(this.onWindowResize, 60));
 }
 
 onWindowResize = () => {
 	console.log('resize');
 }

 handleChange = (e) => {
   e.persist();
   this.change(e);
 }

 render() {
   return (
       <input onChange={this.handleChange} /> ); } } 複製代碼

函數去抖

當事件觸發以後,必須等待某一個時間(N)以後,回調函數纔會執行,倘若再等待的時間內,事件又觸發了則從新再等待時間N,直到事件N內事件不被觸發,那麼最後一次觸發過了事件N後,執行函數。優化

仍是窗口resize,若是一直改變窗口大小,則不會打印1,只有中止改變窗口大小並等待一段時間後,纔會打印1。ui

函數去抖簡單實現:

/** * @param func {Function} 實際要執行的函數 * @param delay {Number} 延遲時間,單位是毫秒(ms) * @return {Function} */

function debounce(fn, delay = 1000) {
  let timer;

  // 返回一個函數,這個函數會在一個時間區間結束後的 delay 毫秒時執行 func 函數
  return function () { 

    // 保存函數調用時的上下文和參數,傳遞給func
    var context = this
    var args = arguments

    // 函數被調用,清除定時器
    clearTimeout(timer)

    // 當返回的函數被最後一次調用後(也就是用戶中止了某個連續的操做),
    // 再過 delay 毫秒就執行 func
    timer = setTimeout(function () {
      fn.apply(context, args);
    }, delay);
  }
}
複製代碼

應用場景,監聽文件變化,重啓應用:

const debounce = require('./debounce');

watcher.on('change', debounce(() => {
    const child = spawn('npm', ['run', 'dev:electron'], {
      cwd,
      detached: true,
      stdio: 'inherit'
    })
    child.unref();
    electron.app.quit();
  }, delay));
複製代碼
相關文章
相關標籤/搜索