Выполните debounce в React.js

328

Как вы выполняете debounce в React.js?

Я хочу отменить handleOnChange.

Я пробовал с debounce(this.handleOnChange, 200), но он не работает.

function debounce(fn, delay) {
  var timer = null;
  return function () {
    var context = this, args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function () {
      fn.apply(context, args);
    }, delay);
  };
}
var SearchBox = React.createClass({

    render:function () {
    return (
    <input  type="search" name="p"
       onChange={this.handleOnChange}/>
    );
    },
    handleOnChange: function (event) {
       //make ajax call
    }
});
  • 0
    Я встречал ту же проблему с вами, превосходные ответы ниже! но я думаю, что вы использовали неправильный способ debounce . здесь, когда onChange={debounce(this.handleOnChange, 200)}/> , он будет вызывать debounce function каждый раз. но на самом деле нам нужно вызвать функцию, возвращаемую функцией debounce.
Теги:

18 ответов

686

В 2018 году: попробуйте обещать разоблачение

Мы часто хотим отменить вызовы API, чтобы избежать затопления серверной части бесполезными запросами.

В 2018 году работа с обратными вызовами (Lodash/Underscore) кажется мне плохой и подверженной ошибкам. Проблемы с параллельным доступом и параллелизмом легко возникают из-за разрешения вызовов API в произвольном порядке.

Я создал небольшую библиотеку для React, чтобы решить ваши проблемы: awesome-debounce-обещание.

Это не должно быть сложнее, чем это:

const searchAPI = text => fetch('/search?text=' + encodeURIComponent(text));

const searchAPIDebounced = AwesomeDebouncePromise(searchAPI, 500);

class SearchInputAndResults extends React.Component {
  state = {
    text: '',
    results: null,
  };

  handleTextChange = async text => {
    this.setState({ text, results: null });
    const result = await searchAPIDebounced(text);
    this.setState({ result });
  };
}

Деблокированная функция гарантирует, что:

  • Вызовы API будут отклонены
  • Функция debounce всегда возвращает обещание
  • разрешит только обещанный последний звонок
  • один this.setState({ result }); произойдет за вызов API

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

componentWillUnmount() {
  this.setState = () => {};
}

Обратите внимание, что Observables (RxJS) также могут отлично подходить для разбора входных данных, но это более мощная абстракция, которая может быть сложнее для правильного изучения/использования.


Все еще хотите использовать обратный вызов?

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

В этом ответе я не определяю функцию устранения неполадок, так как она не очень актуальна, но этот ответ будет отлично работать с _.debounce подчеркивания или lodash, а также с любой предоставленной пользователем функцией устранения неполадок.


ОТЛИЧНАЯ ИДЕЯ:

Поскольку отклоняемые функции сохраняют состояние, мы должны создать одну отклоненную функцию для каждого экземпляра компонента.

ES6 (свойство класса): рекомендуется

class SearchBox extends React.Component {
    method = debounce(() => { 
      ...
    });
}

ES6 (конструктор класса)

class SearchBox extends React.Component {
    constructor(props) {
        super(props);
        this.method = debounce(this.method,1000);
    }
    method() { ... }
}

ES5

var SearchBox = React.createClass({
    method: function() {...},
    componentWillMount: function() {
       this.method = debounce(this.method,100);
    },
});

См. JsFiddle: 3 экземпляра создают 1 запись в журнале за экземпляр (что составляет 3 глобально).


Не хорошая идея:

var SearchBox = React.createClass({
  method: function() {...},
  debouncedMethod: debounce(this.method, 100);
});

Это не будет работать, потому что при создании объекта описания класса this не сам объект, созданный. this.method не возвращает того, что вы ожидаете, потому что this контекст не является самим объектом (который на самом деле еще не существует, кстати, поскольку он только создается).


Не хорошая идея:

var SearchBox = React.createClass({
  method: function() {...},
  debouncedMethod: function() {
      var debounced = debounce(this.method,100);
      debounced();
  },
});

На этот раз вы эффективно создаете дебагованную функцию, которая вызывает ваш this.method. Проблема в том, что вы воссоздаете его при каждом вызове debouncedMethod, поэтому вновь созданная функция debounce ничего не знает о предыдущих вызовах! Вы должны повторно использовать одну и ту же отклоненную функцию с течением времени, иначе удаление не произойдет.


Не хорошая идея:

var SearchBox = React.createClass({
  debouncedMethod: debounce(function () {...},100),
});

Это немного сложно здесь.

Все подключенные экземпляры класса будут использовать одну и ту же функцию, от которой отказались, и чаще всего это не то, что вам нужно! См. JsFiddle: 3 экземпляра производят только 1 запись журнала во всем мире.

Для каждого экземпляра компонента необходимо создать неработающую функцию, а не отдельную деблокированную функцию на уровне класса, которая будет использоваться каждым экземпляром компонента.


Позаботьтесь о пуле событий React

Это связано с тем, что мы часто хотим отсеивать или ограничивать события DOM.

В React объекты событий (т. SyntheticEvent), которые вы получаете в обратных вызовах, объединяются (теперь это задокументировано). Это означает, что после вызова обратного вызова события полученное SyntheticEvent будет помещено обратно в пул с пустыми атрибутами, чтобы уменьшить нагрузку на GC.

Таким образом, если вы SyntheticEvent свойствам SyntheticEvent асинхронно с исходным обратным вызовом (как это может быть в случае, если вы дросселируете/отклоняете), свойства, к которым вы обращаетесь, могут быть удалены. Если вы хотите, чтобы событие никогда не возвращалось в пул, вы можете использовать метод persist().

Без сохранения (поведение по умолчанию: объединенное событие)

onClick = e => {
  alert('sync -> hasNativeEvent=${!!e.nativeEvent}');
  setTimeout(() => {
    alert('async -> hasNativeEvent=${!!e.nativeEvent}');
  }, 0);
};

2-й (асинхронный) напечатает hasNativeEvent=false потому что свойства события были очищены.

С упорством

onClick = e => {
  e.persist();
  alert('sync -> hasNativeEvent=${!!e.nativeEvent}');
  setTimeout(() => {
    alert('async -> hasNativeEvent=${!!e.nativeEvent}');
  }, 0);
};

Второй (асинхронный) напечатает hasNativeEvent=true, потому что persist позволяет избежать помещать событие обратно в бассейне.

Вы можете проверить эти 2 поведения здесь: JsFiddle

Прочитайте ответ Джулена для примера использования persist() с функцией throttle/debounce.

  • 3
    Превосходный ответ, это отлично подходит для установки состояния полей формы как «взаимодействующего» в течение нескольких секунд после того, как они перестают печатать, и затем возможности отмены при отправке формы или onBlur
  • 8
    Обратите внимание, что в ES6 вместо определения вашего метода внутри конструктора (кажется странным) вы можете сделать handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout) на верхнем уровне вашего класса. Вы по-прежнему эффективно устанавливаете элемент экземпляра, но это выглядит немного больше как обычное определение метода. Нет необходимости в constructor если у вас его еще нет. Я полагаю, это в основном стиль предпочтений.
Показать ещё 12 комментариев
198

Неконтролируемые компоненты

Вы можете использовать метод event.persist().

В следующем примере следует использовать знак подчеркивания _.debounce():

var SearchBox = React.createClass({

  componentWillMount: function () {
     this.delayedCallback = _.debounce(function (event) {
       // `event.target` is accessible now
     }, 1000);
  },

  onChange: function (event) {
    event.persist();
    this.delayedCallback(event);
  },

  render: function () {
    return (
      <input type="search" onChange={this.onChange} />
    );
  }

});

Изменить: см. этот JSFiddle


Контролируемые компоненты

Обновление: приведенный выше пример показывает неконтролируемый компонент. Я использую управляемые элементы все время, так что вот еще один пример выше, но без использования event.persist() "обмана".

A Также доступен JSFiddle. Пример без подчеркивания

var SearchBox = React.createClass({
    getInitialState: function () {
        return {
            query: this.props.query
        };
    },

    componentWillMount: function () {
       this.handleSearchDebounced = _.debounce(function () {
           this.props.handleSearch.apply(this, [this.state.query]);
       }, 500);
    },

    onChange: function (event) {
      this.setState({query: event.target.value});
      this.handleSearchDebounced();
    },

    render: function () {
      return (
        <input type="search"
               value={this.state.query}
               onChange={this.onChange} />
      );
    }
});


var Search = React.createClass({
    getInitialState: function () {
        return {
            result: this.props.query
        };
    },

    handleSearch: function (query) {
        this.setState({result: query});
    },

    render: function () {
      return (
        <div id="search">
          <SearchBox query={this.state.result}
                     handleSearch={this.handleSearch} />
          <p>You searched for: <strong>{this.state.result}</strong></p>
        </div>
      );
    }
});

React.render(<Search query="Initial query" />, document.body);

Изменить: обновленные примеры и JSFiddles для изменения 0.12

Изменить: обновленные примеры для решения проблемы, поднятой Себастьяном Лорбером

Изменить: обновлено с помощью jsfiddle, который не использует подчеркивание и использует простой javascript-debounce.

  • 0
    Это не работает для входов. Цель события в дебазированной функции больше не имеет значения ... поэтому вход остается пустым.
  • 0
    не важно, это работает в jsfiddle, просто не в моем env по некоторым причинам ... извините. Я отредактировал твой ответ, чтобы добавить jsfiddle, но я не могу проголосовать, пока ты не отредактируешь свой ответ ..
Показать ещё 13 комментариев
11

Если все, что вам нужно от объекта события, - это получить элемент ввода DOM, решение будет намного проще - просто используйте ref. Обратите внимание, что это требует подчеркивания:

class Item extends React.Component {
    constructor(props) {
        super(props);
        this.saveTitle = _.throttle(this.saveTitle.bind(this), 1000);
    }
    saveTitle(){
        let val = this.inputTitle.value;
        // make the ajax call
    }
    render() {
        return <input 
                    ref={ el => this.inputTitle = el } 
                    type="text" 
                    defaultValue={this.props.title} 
                    onChange={this.saveTitle} />
    }
}
  • 1
    defaultValue это то, что я хочу! Спасибо тебе, Маха :)
7

Я нашел этот пост Джастином Тулком очень полезным. После нескольких попыток, что, как можно было бы воспринимать как более официальный способ с реакцией/редукцией, показывает, что это не удается из-за искусственного объединения событий React. Затем его решение использует некоторое внутреннее состояние для отслеживания значения, измененного/введенного во входных данных, с обратным вызовом сразу после setState который вызывает дросселированное /setState действие, которое показывает некоторые результаты в реальном времени.

import React, {Component} from 'react'
import TextField from 'material-ui/TextField'
import { debounce } from 'lodash'

class TableSearch extends Component {

  constructor(props){
    super(props)

    this.state = {
        value: props.value
    }

    this.changeSearch = debounce(this.props.changeSearch, 250)
  }

  handleChange = (e) => {
    const val = e.target.value

    this.setState({ value: val }, () => {
      this.changeSearch(val)
    })
  }

  render() {

    return (
        <TextField
            className = {styles.field}
            onChange = {this.handleChange}
            value = {this.props.value}
        />
    )
  }
}
6

Использование ES6 CLASS и React 15.xx & lodash.debounce Im использования Реагировать реф здесь, так как потери Ивентов это связывают внутренне.

class UserInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      userInput: ""
    };
    this.updateInput = _.debounce(this.updateInput, 500);
  }


  updateInput(userInput) {
    this.setState({
      userInput
    });
    //OrderActions.updateValue(userInput);//do some server stuff
  }


  render() {
    return ( <div>
      <p> User typed: {
        this.state.userInput
      } </p>
      <input ref = "userValue" onChange = {() => this.updateInput(this.refs.userValue.value) } type = "text" / >
      </div>
    );
  }
}

ReactDOM.render( <
  UserInput / > ,
  document.getElementById('root')
);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>


<div id="root"></div>
5

Здесь много полезной информации, но, чтобы быть кратким. Это работает для меня...

import React, {Component} from 'react';
import _ from 'lodash';

class MyComponent extends Component{
      constructor(props){
        super(props);
        this.handleChange = _.debounce(this.handleChange.bind(this),700);
      }; 
5

Поработав некоторое время с вводом текста и не найдя идеального решения самостоятельно, я нашел это на npm https://www.npmjs.com/package/react-debounce-input

Вот простой пример:

import React from 'react';
import ReactDOM from 'react-dom';
import {DebounceInput} from 'react-debounce-input';

class App extends React.Component {
state = {
    value: ''
};

render() {
    return (
    <div>
        <DebounceInput
        minLength={2}
        debounceTimeout={300}
        onChange={event => this.setState({value: event.target.value})} />

        <p>Value: {this.state.value}</p>
    </div>
    );
}
}

const appRoot = document.createElement('div');
document.body.appendChild(appRoot);
ReactDOM.render(<App />, appRoot);

Компонент DebounceInput принимает все реквизиты, которые вы можете назначить обычному элементу ввода. Попробуйте это на codepen

Я надеюсь, что это поможет кому-то еще и сэкономит им время.

4

Вы можете использовать метод Lodash debounce https://lodash.com/docs/4.17.5#debounce. Это просто и эффективно.

import * as lodash from lodash;

const update = (input) => {
    // Update the input here.
    console.log('Input ${input}');     
}

const debounceHandleUpdate = lodash.debounce((input) => update(input), 200, {maxWait: 200});

doHandleChange() {
   debounceHandleUpdate(input);
}

Вы также можете отменить метод debounce, используя метод ниже.

this.debounceHandleUpdate.cancel();

Надеюсь, это поможет вам. Ура !!

3

Если вы используете redux, вы можете сделать это очень элегантно с помощью промежуточного программного обеспечения. Вы можете определить промежуточное ПО Debounce как:

var timeout;
export default store => next => action => {
  const { meta = {} } = action;
  if(meta.debounce){
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      next(action)
    }, meta.debounce)
  }else{
    next(action)
  }
}

Затем вы можете добавить создателей debouncing в action, например:

export default debouncedAction = (payload) => ({
  type : 'DEBOUNCED_ACTION',
  payload : payload,
  meta : {debounce : 300}
}

На самом деле уже промежуточное программное обеспечение вы можете выйти из npm, чтобы сделать это за вас.

  • 0
    я думаю, что это промежуточное ПО должно быть первым, которое будет выполнено в applyMiddleware(...) если у нас много
2

Вот пример, который я придумал, который обертывает другой класс с помощью debouncer. Это хорошо пригодится для превращения в функцию декоратора/более высокого порядка:

export class DebouncedThingy extends React.Component {
    static ToDebounce = ['someProp', 'someProp2'];
    constructor(props) {
        super(props);
        this.state = {};
    }
    // On prop maybe changed
    componentWillReceiveProps = (nextProps) => {
        this.debouncedSetState();
    };
    // Before initial render
    componentWillMount = () => {
        // Set state then debounce it from here on out (consider using _.throttle)
        this.debouncedSetState();
        this.debouncedSetState = _.debounce(this.debouncedSetState, 300);
    };
    debouncedSetState = () => {
        this.setState(_.pick(this.props, DebouncedThingy.ToDebounce));
    };
    render() {
        const restOfProps = _.omit(this.props, DebouncedThingy.ToDebounce);
        return <Thingy {...restOfProps} {...this.state} />
    }
}
1

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

  updateUserProfileField(fieldName) {
    const handler = throttle(value => {
      console.log(fieldName, value);
    }, 400);
    return evt => handler(evt.target.value.trim());
  }

и в вашем методе render вы можете:

<input onChange={this.updateUserProfileField("givenName").bind(this)}/>

метод updateUserProfileField будет создавать отдельную функцию каждый раз, когда вы ее вызываете.

Примечание не пытайтесь вернуть обработчик напрямую, например, это не сработает:

 updateUserProfileField(fieldName) {
    return evt => throttle(value => {
      console.log(fieldName, value);
    }, 400)(evt.target.value.trim());
  }

причина, по которой это не сработает, потому что это будет генерировать новую функцию дроссельной заслонки каждый раз, когда вызванное событие вместо использования одной и той же функции дроссельной заслонки, так что дроссель будет бесполезен;)

Также, если вы используете debounce или throttle, вам не нужны setTimeout или clearTimeout, на самом деле мы их используем: P

1

Я искал решение одной и той же проблемы и наткнулся на этот поток, а также на некоторые другие, но у них была одна и та же проблема: если вы пытаетесь выполнить функцию handleOnChange, и вам нужно значение из целевого объекта, вы получите cannot read property value of null или некоторую такую ​​ошибку. В моем случае мне также необходимо сохранить контекст this внутри дебявленной функции, так как я выполняю потоковое действие. Здесь мое решение, оно хорошо работает для моего прецедента, поэтому я оставляю его здесь, если кто-нибудь встретит этот поток:

// at top of file:
var myAction = require('../actions/someAction');

// inside React.createClass({...});

handleOnChange: function (event) {
    var value = event.target.value;
    var doAction = _.curry(this.context.executeAction, 2);

    // only one parameter gets passed into the curried function,
    // so the function passed as the first parameter to _.curry()
    // will not be executed until the second parameter is passed
    // which happens in the next function that is wrapped in _.debounce()
    debouncedOnChange(doAction(myAction), value);
},

debouncedOnChange: _.debounce(function(action, value) {
    action(value);
}, 300)
1

Вместо того, чтобы обертывать handleOnChange в debounce(), почему бы не обернуть вызов ajax внутри функции обратного вызова внутри debounce, тем самым не разрушив объект события. Так что-то вроде этого:

handleOnChange: function (event) {
   debounce(
     $.ajax({})
  , 250);
}
  • 2
    Поскольку объект события не является неизменным и уничтожается с помощью ReactJS, поэтому даже если вы обернетесь и достигнете захвата замыкания, код потерпит неудачу.
0

вы можете использовать Tlence https://www.npmjs.com/package/tlence

function log(server) {
  console.log('connecting to', server);
}

const debounceLog = debounce(log, 5000);
// just run last call to 5s
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
0

Просто еще один вариант с недавними реакциями и лодашами.

class Filter extends Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired
  }

  state = {
    initialText: '',
    text: ''
  }

  constructor (props) {
    super(props)

    this.setText = this.setText.bind(this)
    this.onChange = _.fp.debounce(500)(this.onChange.bind(this))
  }

  static getDerivedStateFromProps (nextProps, prevState) {
    const { text } = nextProps

    if (text !== prevState.initialText) {
      return { initialText: text, text }
    }

    return null
  }

  setText (text) {
    this.setState({ text })
    this.onChange(text)
  }

  onChange (text) {
    this.props.onChange(text)
  }

  render () {
    return (<input value={this.state.text} onChange={(event) => this.setText(event.target.value)} />)
  }
}
0

Решение Julen довольно сложно прочитать, здесь более ясный и точный код для всех, кто наткнулся на него, основываясь на заголовке, а не на крошечных деталях вопроса.

tl; dr version: когда вы обновляете наблюдателей, вместо этого отправляйте вызов метода расписания, а это, в свою очередь, будет фактически уведомлять наблюдателей (или выполнять ajax и т.д.)

Завершите jsfiddle с примером компонента http://jsfiddle.net/7L655p5L/4/

var InputField = React.createClass({

    getDefaultProps: function () {
        return {
            initialValue: '',
            onChange: null
        };
    },

    getInitialState: function () {
        return {
            value: this.props.initialValue
        };
    },

    render: function () {
        var state = this.state;
        return (
            <input type="text"
                   value={state.value}
                   onChange={this.onVolatileChange} />
        );
    },

    onVolatileChange: function (event) {
        this.setState({ 
            value: event.target.value 
        });

        this.scheduleChange();
    },

    scheduleChange: _.debounce(function () {
        this.onChange();
    }, 250),

    onChange: function () {
        var props = this.props;
        if (props.onChange != null) {
            props.onChange.call(this, this.state.value)
        }
    },

});
  • 2
    Разве это не сделает состояние / время отклика глобальным для всех экземпляров InputField, потому что он создан с определением класса? Может быть, это то, что вы хотите, но это стоит отметить в любом случае.
  • 1
    опасно, если монтируется несколько раз в дом, проверьте stackoverflow.com/questions/23123138/…
Показать ещё 1 комментарий
-1

Вы также можете использовать самоналоженный микшинг, что-то вроде этого:

var DebounceMixin = {
  debounce: function(func, time, immediate) {
    var timeout = this.debouncedTimeout;
    if (!timeout) {
      if (immediate) func();
      this.debouncedTimeout = setTimeout(function() {
        if (!immediate) func();
        this.debouncedTimeout = void 0;
      }.bind(this), time);
    }
  }
};

И затем используйте его в своем компоненте следующим образом:

var MyComponent = React.createClass({
  mixins: [DebounceMixin],
  handleClick: function(e) {
    this.debounce(function() {
      this.setState({
        buttonClicked: true
      });
    }.bind(this), 500, true);
  },
  render: function() {
    return (
      <button onClick={this.handleClick}></button>
    );
  }
});
  • 2
    Это не дебат, это «задержка». Дебандирование сбрасывает время ожидания каждого события, которое происходит до истечения времени ожидания. -1
  • 0
    @ Генрик Плохо, ты прав. Кстати, это легко сделать, как это.
Показать ещё 2 комментария
-6

Не нужно тонны локальных переменных для приличной функции газа. Целью функции регулирования является сокращение ресурсов браузера, а не применение таких дополнительных затрат, которые вы используете еще больше. В качестве доказательства этого утверждения я разработал дроссельную функцию, которая имеет только 4 "зависающие" переменные. (Переменная "зависания" - это переменная, которая никогда не собирается сборщиком мусора, потому что на нее всегда ссылается функция, которая потенциально может быть вызвана, тем самым впитывая память.) Горстка дроссельных функций обычно не приносит никакого вреда; но, если есть тысячи дросселированных функций, то памяти становится мало, если вы используете действительно неэффективную функцию дросселя. Мое решение ниже.

var timenow=self.performance ? performance.now.bind(performance) : Date.now;
function throttle(func, alternateFunc, minInterval) {
    var lastTimeWent = -minInterval, freshArguments=null;
    function executeLater(){
        func.apply(null, freshArguments);
        freshArguments = null;
        lastTimeWent = 0;
    }
    return function() {
        var newTimeWent = timenow();
        if ((newTimeWent-lastTimeWent) > minInterval) {
            lastTimeWent = newTimeWent;
            return func.apply(null, arguments);
        } else {
            if (freshArguments === null)
                setTimeout(executeLater, minInterval-(newTimeWent-lastTimeWent));
            freshArguments = arguments;
            if (typeof alternateFunc === "function")
                return alternateFunc.apply(null, arguments);
        }
    };
}

Затем, чтобы обернуть эту функцию регулирования вокруг EventTarget для таких вещей, как щелчки DOM, события окна, XMLHttpRequests onprogress, FileReader onprogress и т.д., Например, так:

var tfCache = []; // throttled functions cache
function listen(source, eventName, func, _opts){
    var i = 0, Len = tfCache.length, cF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (tfCache[i] === func &&
              tfCache[i+1] === (options.ALTERNATE||null) &&
              tfCache[i+2] === (options.INTERVAL||200)
            ) break a;
        cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200);
        tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF);
    }
    source.addEventListener(eventName, cF || tfCache[i+3], _opts);
    return cF === null; // return whether it used the cache or not
};
function mute(source, eventName, func, _opts){
    var options = _opts || {};
    for (var i = 0, Len = tfCache.length; i < Len; i += 4)
        if (tfCache[i] === func &&
          tfCache[i+1] === (options.ALTERNATE||null) &&
          tfCache[i+2] === (options.INTERVAL||200)
        ) {
            source.removeEventListener(eventName, tfCache[i+3], options);
            return true;
        }
    return false;
}

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

(function(){"use strict";
// The function throttler //
var timenow=self.performance ? performance.now.bind(performance) : Date.now;
function throttle(func, alternateFunc, minInterval) {
    var lastTimeWent = -minInterval, freshArguments=null;
    function executeLater(){
        func.apply(null, freshArguments);
        freshArguments = null;
        lastTimeWent = 0;
    }
    return function() {
        var newTimeWent = timenow();
        if ((newTimeWent-lastTimeWent) > minInterval) {
            lastTimeWent = newTimeWent;
            return func.apply(null, arguments);
        } else {
            if (freshArguments === null)
                setTimeout(executeLater,minInterval-(newTimeWent-lastTimeWent));
            freshArguments = arguments;
            if (typeof alternateFunc === "function")
                return alternateFunc.apply(this, arguments);
        }
    };
}
// The EventTarget wrapper: //
var tfCache = []; // throttled functions cache
function listen(source, eventName, func, _opts){
    var i = 0, Len = tfCache.length, cF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (tfCache[i] === func &&
              tfCache[i+1] === (options.ALTERNATE||null) &&
              tfCache[i+2] === (options.INTERVAL||200)
            ) break a;
        cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200);
        tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF);
    }
    source.addEventListener(eventName, cF || tfCache[i+3], _opts);
    return cF === null; // return whether it used the cache or not
};
function mute(source, eventName, func, _opts){
    var options = _opts || {};
    for (var i = 0, Len = tfCache.length; i < Len; i += 4)
        if (tfCache[i] === func &&
          tfCache[i+1] === (options.ALTERNATE||null) &&
          tfCache[i+2] === (options.INTERVAL||200)
        ) {
            source.removeEventListener(eventName, tfCache[i+3], options);
            return true;
        }
    return false;
}
// Finally, the color changing button: //
function randHex(){ // weighted towards the ends of the scales for contrast
    var rand = Math.random()*2 - 1; // equally offcenter it from one
    var sign = rand < 0 ? -1 : 1; // get a sign-ish value
    rand = Math.sqrt(rand * sign) * sign; // stretch it further from zero
    rand = 128 + rand * 128; // next, recenter it to range from 0 to 255 
    var str = (rand | 0).toString(16); // make an integer string
    while (str.length < 2) str = "0" + str; // pad it
    return str; // finally, return it
}
var clickerEle = document.getElementById("clicker");
var dropperEle = document.getElementById("droppedClicks");
var deDs = dropperEle.dataset; // deDs = droperEle DataSet
var dropSkips = 0;
function whenClick(){
    if (dropSkips > 10) { // if the user clicked fast enough
        mute(clickerEle, "click", whenClick, theOptions);
        dropperEle.textContent = "You won with " + dropSkips + 
            " clicks per second! The button no longer changes color";
    }
    dropSkips = 0;
    deDs ? delete deDs.numdrops : dropperEle.removeAttribute("data-numdrops");
    clickerEle.setAttribute("style", "background:#"+randHex()+randHex()+randHex());
}
var theOptions = {
    ALTERNATE: function(){
        // whenever the main function is skipped:
        deDs.numdrops = dropSkips += 1;
    },
    INTERVAL: 2000,
    passive: true
};
listen(clickerEle, "click", whenClick, theOptions);
whenClick(); // to start changing the color
document.body.addEventListener("contextmenu", function(x){x.preventDefault()});
})();
#droppedClicks[data-numdrops]::before {
    content: "Dropped " attr(data-numdrops) " clicks";
    color: green;
}
Click the button below as fast as you can! You win when you are able to click the button more than ten times in a single second ().<br />
<br />
<button id="clicker"><h3>Click me</h3></button>
<div id="droppedClicks"></div>

Сохранив этот ответ, я обнаружил, что постоянные отрицательные голоса сообщества токсичных SO препятствуют запуску этого фрагмента. Поэтому вот ссылка на JSFiddle: https://jsfiddle.net/t7ymkzLx/2/

По умолчанию это ограничивает функцию до одного вызова каждые 200 мс. Чтобы изменить интервал на другое количество миллисекунд, затем optionsObject.INTERVAL опцию optionsObject.INTERVAL в аргументе параметров и установите для нее желаемое минимальное значение в миллисекундах между выполнениями. (Поскольку таймеры не всегда являются наиболее точными). Если у вас есть точный минимальный требуемый интервал, я бы порекомендовал вам вычесть один или два из желаемого optionsObject.INTERVAL чтобы он всегда выполнялся, по крайней мере, когда это необходимо. Если вам нужно что-то сделать с аргументами функции удушения, когда выполнение функции optionsObject.ALTERNATE (из-за избыточных вызовов), то используйте опцию optionsObject.ALTERNATE. Этот "ALTERNATE" является функцией, которая вызывается немедленно вместо основной функции всякий раз, когда прекращается вызов основной функции. Например, если вы используете функцию удушения для EventTarget, но хотите preventDefault() для пропущенных событий, используйте {ALTERNATE: function(evt){ evt.preventDefault(); }} {ALTERNATE: function(evt){ evt.preventDefault(); }} для объекта параметров.

  • 1
    Боже мой ... все эти вложенные коды и переключатели ... И оператор тильды минус один ...
  • 0
    Это наименее полезный код, который я видел, и худший в удобочитаемости
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню