[Оптимизация производительности JS] Функция устранения дребезга (debounce) и функция дросселирования (throttle)

внешний интерфейс JavaScript React.js

предисловие

Это тема старой поговорки, и причина в том, что одна из причин в том, что нет больше беспокойства раньше. Некоторые проблемы, с которыми сталкиваются в ближайшем будущем, необходимо использовать эти два совета; причина 2, эти два навыка оптимизации не мал; причина три, а ближе рассмотрено кстати.

В процессе разработки вы можете столкнуться со следующими ситуациями:

  • мониторWindowобъектresize,scrollмероприятие
  • Слушайте во время перетаскиванияmousemove
  • При вводе текста входная строка обрабатывается, например, дляmarkdwonПеревести вhtml
  • Отслеживайте изменения файлов и перезапускайте службу

В первом и третьем случаях событие часто срабатывает в течение короткого периода времени.Если в событии присутствует большое количество вычислений, частые манипуляции с DOM, загрузка ресурсов и другие тяжелые действия, это может привести к зависанию пользовательского интерфейса, или даже заставить браузер зависнуть в тяжелых случаях. В четвертом случае некоторые разработчики любят сохранять отредактированные файлы, нажимая несколько разCtrl+S, если вы перезапустите службу быстро, вы все равно можете ее удерживать, но если вы перезапустите приложение, оно может перезапуститься без необходимости много раз.

Ввиду вышеуказанного ряда потребностей существуетdebounceа такжеthrottleДва решения.

Функция дроссельной заслонки

Функция выполняется за один цикл, например, даваяwindowсвязать одинresizeПосле события печатать 1 до тех пор, пока размер окна не изменится.Если функция throttling не используется, то при настройке окна мы обнаружим, что консоль продолжает печатать1, но после использования функции throttling мы обнаружим, что в процессе настройки она каждый раз только печатается1.

Простая реализация функции дросселирования:

/**
*
* @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);
       }
   };
}

Используемый метод очень прост:

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.

Простая реализация функции debounce:

/**
 * @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));