Чтение одного модуля npm в день (4) — Throttle-debounce

Node.js внешний интерфейс исходный код JavaScript NPM

Серия статей:

  1. Читать один модуль npm в день (1) — имя пользователя
  2. Читать один модуль npm в день (2) - mem
  3. Читать один модуль npm в день (3) — mimic-fn

Знание дескрипторов атрибутов, представленное в предыдущей статье, слишком теоретическое, и я прочитал его сегодня.throttle-debounceМодули будут намного практичнее и их можно будет часто использовать в работе.

вступление в одно предложение

Модуль npm для чтения сегодня:throttle-debounce,Это обеспечиваетthrottleа такжеdebounceДве функции: Throttle означает дросселирование, debounce означает антиджиттер, с помощью которого вы можете ограничить частоту выполнения функций и избежать проблем с производительностью, вызванных многократным выполнением функций за короткий промежуток времени Текущая версия пакета 2.0.1, скачать каждую неделю Сумма 63 000.

Применение

Первый выбор должен быть представленthrottleа такжеdebounce, все они могут быть использованы длярегулирование функцииЭто повышает производительность, но все же есть некоторые отличия:

  • debounce: объединить события, которые запускаются несколько раз за короткий промежуток времени, в одно выполнение функции ответа на событие (часто выполняется при первом событии или при срабатывании последнего события), то есть только в течение этого периода времени.однаждыФактически выполнить функцию ответа на событие.
  • дроссель: если одно и то же событие срабатывает несколько раз в течение короткого промежутка времени, функция реагирования на событие будет выполняться с меньшим интервалом времени, то есть это может быть возможно в течение этого промежутка времени.неоднократноВыполните функцию ответа на событие.

Хотя ожидание лифта занимает более десяти минут каждый день, вы все равно можете взять лифт в качестве примера:

  • debounce: Если бы я был в лифте и собирался закрыть дверь, когда А хотел бы воспользоваться лифтом, я бы из вежливости нажал кнопку открытия двери, а затем попытался бы закрыть дверь после того, как он вошел в лифт; после того, как А вошел в лифт, обнаружил, что Б я тоже хочу подняться на лифте, потом опять из вежливости нажму на кнопку двери и буду ждать, пока он войдет в лифт. Поэтому, если кто-то все время хочет подняться на лифте, я буду продолжать откладывать время нажатия кнопки закрытия до тех пор, пока никто не захочет пользоваться лифтом (если я сделаю это в реальной жизни, я думаю, что ничего не смогу сделать, кроме как сесть на лифт). лифт каждый день.Сделано).
  • дроссель: У меня на самом деле есть работа каждый день, невозможно бесконечно ждать кого-то в лифте. Так что на этот раз я буду немного более своевольным и подожду всего секунд 30. Неважно, захочет ли кто-нибудь подняться на лифте, я нажму кнопку закрытия и уйду.

Из приведенных выше двух примеров видно, что самая большая разница между ними заключается в том, что пока есть событие (кто-то хочет подняться на лифте), если вы используетеthrottleметод, то функция ответа на событие будет выполнена в течение определенного периода времени (я нажимаю кнопку закрытия в течение 30 секунд); если используетсяdebounceметод, то он будет выполнен только после того, как событие перестанет происходить (я обнаружил, что никто не хочет пользоваться лифтом).

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

See the Pen The Difference Between Throttling, Debouncing, and Neither by Elvin Peng (@elvinn) on CodePen.

дляthrottle-debounce, его простое использование выглядит следующим образом:

import { throttle, debounce } from 'throttle-debounce';

function foo() { console.log('foo..'); }
function bar() { console.log('bar..'); }

const fooWrapper = throttle(200, foo);

for (let i = 1; i < 10; i++) {
  setTimeout(fooWrapper, i * 30);
}

// => foo 执行了三次
// => foo..
// => foo..
// => foo..

const barWrapper = debounce(200, bar);

for (let i = 1; i < 10; i++) {
  setTimeout(barWrapper, i * 30);
}

// => bar 执行了一次 
// => bar..

Изучение исходного кода

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

После упрощения исходного кода измените его следующим образом:

// 源码 4-1
function throttle(delay, callback) {
  let timeoutID;
  let lastExec = 0;

  function wrapper() {
    const self = this;
    const elapsed = Number(new Date()) - lastExec;
    const args = arguments;

    function exec() {
      lastExec = Number(new Date());
      callback.apply(self, args);
    }

    clearTimeout(timeoutID);

    if (elapsed > delay) {
      exec();
    } else {
      timeoutID = setTimeout(exec, delay - elapsed);
    }
  }

  return wrapper;
}

Логика всего кода очень понятна, всего три шага:

  1. Вычислить время, прошедшее с момента последнего выполнения функцииelapsed, и сбрасывает ранее установленный таймер.
  2. Если прошедшее время превышает установленный интервалdelay, затем немедленно выполните функцию и обновите время выполнения самой последней функции.
  3. Если прошедшее время меньше установленного интервалаdelay, затем черезsetTimeoutНастройте счетчик и включите функциюdelay - elapsedВыполнить по прошествии времени.

Исходный код 4-1 не сложно понять, но нужно обратить вниманиеthisиспользование:

function throttle(delay, callback) {
    // ...
    function wrapper() {
    	const self = this;
        const args = arguments;
        // ...
        
        function exec() {
            // ...
	      	callback.apply(self, args);
    	}
        
    }
}

В приведенном выше коде с помощьюselfвременное хранение переменныхthisзначение, так что вexecчерез функциюcallback.apply(self, args)пройти в правильномthisзначение, этот подход вЗакрытиеОн очень часто используется в связанных вызовах функций. именно потому, что здесьthisобработки, поэтому могут быть достигнуты следующие возможности:

function foo() { console.log(this.name);  }

const fooWithName = throttle(200, foo);

const obj = {name: 'elvin'};

fooWithName.call(obj, 'elvin');

// => 'elvin'

реализация устранения ложных срабатываний

из-заdebouncen просто задерживает время выполнения функции, у него нетthrottleВозможность, которая гарантированно будет выполняться время от времени, поэтому ее проще реализовать:

function debounce(delay, callback) {
  let timeoutID;

  function wrapper() {
    const self = this;
    const args = arguments;

    function exec() {
      callback.apply(self, args);
    }

    clearTimeout(timeoutID);

    timeoutID = setTimeout(exec, delay);
  }

  return wrapper;
}

Объедините приведенный выше код сthrottleПо сравнению с реализованным кодом можно обнаружить, что он удаленelapsedКод после соответствующей логики, большая часть остального кода точно такая же, поэтомуdebounceФункция может помочьthrottleРеализация функцииthrottle-debounceЭто также сделано в исходном коде),throttleфункция также может использоватьdebounceреализация функции.

Пример сценариев использования дросселя и устранения дребезга

throttleа такжеdebounceОн подходит для сценариев, когда пользователи часто выполняют одну и ту же операцию за короткий промежуток времени, например:

  • Пользователь перетаскивает окно браузера, чтобы изменить размер окна, вызываяresizeмероприятие.
  • Пользователь перемещает мышь, вызываяmousemoveи т.д. события.
  • Пользователь вводит данные в поле ввода, вызываяkeydown | keypress | keyinput | keyupи т.д. события.
  • Пользователь прокручивает экран, вызываяscrollмероприятие.
  • После того, как пользователь нажмет кнопку, поскольку запрос API требует времени и не видит ответа сразу, кнопка может срабатывать постоянно.clickмероприятие.

Я искал много информации в Интернете и обнаружил, что сценарии использования двух функций иногда противоречат друг другу, например, некоторые говорят, что нужно использовать ввод в поле поиска.debounceОграничьте ток, чтобы уменьшить нагрузку на сервер; некоторые говорят, что используйтеthrottleДостаточно ограничить ток, что может быстрее вернуть результаты поиска пользователя.

На мой взгляд, нет такой вещи, как сценарий, который должен быть использован.throttleа такжеdebounceОдин из методов лучше другого, и вам часто нужно рассматривать и выбирать в соответствии со своей ситуацией:

  • Когда занятость ЦП, ГП, трафика, сервера и других ресурсов функцией реагирования на события находится в допустимом диапазоне, вы можете использоватьthrottleОграничение потока обеспечивает лучший пользовательский опыт.
  • Когда функция реагирования на события занимает большое количество ресурсов, таких как ЦП, ГП, трафик и сервер, вы можете использоватьdebounceВыполните более сильное ограничение тока, тем самым снизив нагрузку.

напиши в конце

throttle-debounceИсточник меня несколько дней назад и смотрите наSindreСтиль кода написанного модуля совершенно другой.Количество строк комментариев в его коде примерно в три раза превышает количество строк кода, а параметры функции полностью закомментированы.Это должно быть хорошо, но для мне читать исходный код, мне было не легче сказать, но из-за следующей обработки необязательных параметров мне было труднее читать:

// 源码 4-2

/**
 *
 * @param  {Number}    delay
 * @param  {Boolean}   [noTrailing]
 * @param  {Function}  callback
 * @param  {Boolean}   [debounceMode]
 *
 * @return {Function}  A new, throttled, function.
 */
export default function ( delay, noTrailing, callback, debounceMode ) {
    // `noTrailing` defaults to falsy.
	if ( typeof noTrailing !== 'boolean' ) {
		debounceMode = callback;
		callback = noTrailing;
		noTrailing = undefined;
	}
    
    // ...
}

В исходном коде 4-2 по комментариям видно, чтоnoTrailingа такжеdebounceModeнеобязательный параметр,delayи обратный вызов являются обязательными параметрами, тогда это будут необязательные параметрыnoTrailingобязательный параметрcallbackПрежде еще раз оцените код в функции: еслиnoTrailingявляется функцией, значение должно использоваться какcallback, а потомnoTrailingустановить по умолчаниюundefined.

Я не могу не вздохнуть, что это действительно эффектная операция. Даже если она совместима с ES5, есть лучший способ написать ее. Вот лучший способ написать это, когда я лично думаю, что синтаксис ES6 доступен:

export default function (dalay, noTrailing, options = {
    callback = false,
    debounceMode = false,
} = {}) {
    // ...
}

Обо мне: окончил Хуэк, работающий в Tencent,блог ЭлвинаДобро пожаловать в гости ^_^