Projekt

Obecné

Profil

Stáhnout (5.96 KB) Statistiky
| Větev: | Revize:
1
var isObject = require('./isObject'),
2
    now = require('./now'),
3
    toNumber = require('./toNumber');
4

    
5
/** Error message constants. */
6
var FUNC_ERROR_TEXT = 'Expected a function';
7

    
8
/* Built-in method references for those with the same name as other `lodash` methods. */
9
var nativeMax = Math.max,
10
    nativeMin = Math.min;
11

    
12
/**
13
 * Creates a debounced function that delays invoking `func` until after `wait`
14
 * milliseconds have elapsed since the last time the debounced function was
15
 * invoked. The debounced function comes with a `cancel` method to cancel
16
 * delayed `func` invocations and a `flush` method to immediately invoke them.
17
 * Provide `options` to indicate whether `func` should be invoked on the
18
 * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
19
 * with the last arguments provided to the debounced function. Subsequent
20
 * calls to the debounced function return the result of the last `func`
21
 * invocation.
22
 *
23
 * **Note:** If `leading` and `trailing` options are `true`, `func` is
24
 * invoked on the trailing edge of the timeout only if the debounced function
25
 * is invoked more than once during the `wait` timeout.
26
 *
27
 * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
28
 * until to the next tick, similar to `setTimeout` with a timeout of `0`.
29
 *
30
 * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
31
 * for details over the differences between `_.debounce` and `_.throttle`.
32
 *
33
 * @static
34
 * @memberOf _
35
 * @since 0.1.0
36
 * @category Function
37
 * @param {Function} func The function to debounce.
38
 * @param {number} [wait=0] The number of milliseconds to delay.
39
 * @param {Object} [options={}] The options object.
40
 * @param {boolean} [options.leading=false]
41
 *  Specify invoking on the leading edge of the timeout.
42
 * @param {number} [options.maxWait]
43
 *  The maximum time `func` is allowed to be delayed before it's invoked.
44
 * @param {boolean} [options.trailing=true]
45
 *  Specify invoking on the trailing edge of the timeout.
46
 * @returns {Function} Returns the new debounced function.
47
 * @example
48
 *
49
 * // Avoid costly calculations while the window size is in flux.
50
 * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
51
 *
52
 * // Invoke `sendMail` when clicked, debouncing subsequent calls.
53
 * jQuery(element).on('click', _.debounce(sendMail, 300, {
54
 *   'leading': true,
55
 *   'trailing': false
56
 * }));
57
 *
58
 * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
59
 * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
60
 * var source = new EventSource('/stream');
61
 * jQuery(source).on('message', debounced);
62
 *
63
 * // Cancel the trailing debounced invocation.
64
 * jQuery(window).on('popstate', debounced.cancel);
65
 */
66
function debounce(func, wait, options) {
67
  var lastArgs,
68
      lastThis,
69
      maxWait,
70
      result,
71
      timerId,
72
      lastCallTime,
73
      lastInvokeTime = 0,
74
      leading = false,
75
      maxing = false,
76
      trailing = true;
77

    
78
  if (typeof func != 'function') {
79
    throw new TypeError(FUNC_ERROR_TEXT);
80
  }
81
  wait = toNumber(wait) || 0;
82
  if (isObject(options)) {
83
    leading = !!options.leading;
84
    maxing = 'maxWait' in options;
85
    maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
86
    trailing = 'trailing' in options ? !!options.trailing : trailing;
87
  }
88

    
89
  function invokeFunc(time) {
90
    var args = lastArgs,
91
        thisArg = lastThis;
92

    
93
    lastArgs = lastThis = undefined;
94
    lastInvokeTime = time;
95
    result = func.apply(thisArg, args);
96
    return result;
97
  }
98

    
99
  function leadingEdge(time) {
100
    // Reset any `maxWait` timer.
101
    lastInvokeTime = time;
102
    // Start the timer for the trailing edge.
103
    timerId = setTimeout(timerExpired, wait);
104
    // Invoke the leading edge.
105
    return leading ? invokeFunc(time) : result;
106
  }
107

    
108
  function remainingWait(time) {
109
    var timeSinceLastCall = time - lastCallTime,
110
        timeSinceLastInvoke = time - lastInvokeTime,
111
        timeWaiting = wait - timeSinceLastCall;
112

    
113
    return maxing
114
      ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
115
      : timeWaiting;
116
  }
117

    
118
  function shouldInvoke(time) {
119
    var timeSinceLastCall = time - lastCallTime,
120
        timeSinceLastInvoke = time - lastInvokeTime;
121

    
122
    // Either this is the first call, activity has stopped and we're at the
123
    // trailing edge, the system time has gone backwards and we're treating
124
    // it as the trailing edge, or we've hit the `maxWait` limit.
125
    return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
126
      (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
127
  }
128

    
129
  function timerExpired() {
130
    var time = now();
131
    if (shouldInvoke(time)) {
132
      return trailingEdge(time);
133
    }
134
    // Restart the timer.
135
    timerId = setTimeout(timerExpired, remainingWait(time));
136
  }
137

    
138
  function trailingEdge(time) {
139
    timerId = undefined;
140

    
141
    // Only invoke if we have `lastArgs` which means `func` has been
142
    // debounced at least once.
143
    if (trailing && lastArgs) {
144
      return invokeFunc(time);
145
    }
146
    lastArgs = lastThis = undefined;
147
    return result;
148
  }
149

    
150
  function cancel() {
151
    if (timerId !== undefined) {
152
      clearTimeout(timerId);
153
    }
154
    lastInvokeTime = 0;
155
    lastArgs = lastCallTime = lastThis = timerId = undefined;
156
  }
157

    
158
  function flush() {
159
    return timerId === undefined ? result : trailingEdge(now());
160
  }
161

    
162
  function debounced() {
163
    var time = now(),
164
        isInvoking = shouldInvoke(time);
165

    
166
    lastArgs = arguments;
167
    lastThis = this;
168
    lastCallTime = time;
169

    
170
    if (isInvoking) {
171
      if (timerId === undefined) {
172
        return leadingEdge(lastCallTime);
173
      }
174
      if (maxing) {
175
        // Handle invocations in a tight loop.
176
        clearTimeout(timerId);
177
        timerId = setTimeout(timerExpired, wait);
178
        return invokeFunc(lastCallTime);
179
      }
180
    }
181
    if (timerId === undefined) {
182
      timerId = setTimeout(timerExpired, wait);
183
    }
184
    return result;
185
  }
186
  debounced.cancel = cancel;
187
  debounced.flush = flush;
188
  return debounced;
189
}
190

    
191
module.exports = debounce;
(341-341/634)