diff --git a/lodash.js b/lodash.js index 6c6568ea6..85d6478b3 100644 --- a/lodash.js +++ b/lodash.js @@ -732,14 +732,14 @@ * implicitly or explicitly included in the build. * * The chainable wrapper functions are: - * `after`, `assign`, `at`, `bind`, `bindAll`, `bindKey`, `callback`, `chain`, - * `chunk`, `compact`, `compose`, `concat`, `constant`, `countBy`, `create`, - * `curry`, `debounce`, `defaults`, `defer`, `delay`, `difference`, `drop`, - * `dropRight`, `dropRightWhile`, `dropWhile`, `filter`, `flatten`, `forEach`, - * `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `functions`, - * `groupBy`, `indexBy`, `initial`, `intersection`, `invert`, `invoke`, `keys`, - * `keysIn`, `map`, `mapValues`, `matches`, `memoize`, `merge`, `mixin`, - * `negate`, `noop`, `omit`, `once`, `pairs`, `partial`, `partialRight`, + * `after`, `assign`, `at`, `before`, `bind`, `bindAll`, `bindKey`, `callback`, + * `chain`, `chunk`, `compact`, `compose`, `concat`, `constant`, `countBy`, + * `create`, `curry`, `debounce`, `defaults`, `defer`, `delay`, `difference`, + * `drop`, `dropRight`, `dropRightWhile`, `dropWhile`, `filter`, `flatten`, + * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`, + * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`, + * `invoke`, `keys`, `keysIn`, `map`, `mapValues`, `matches`, `memoize`, `merge`, + * `mixin`, `negate`, `noop`, `omit`, `once`, `pairs`, `partial`, `partialRight`, * `partition`, `pick`, `pluck`, `property`, `pull`, `pullAt`, `push`, `range`, * `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, `sortBy`, * `splice`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `tap`, @@ -5440,14 +5440,13 @@ /*--------------------------------------------------------------------------*/ /** - * Creates a function that executes `func`, with the `this` binding and - * arguments of the created function, only after being called `n` times. + * The opposite of `_.before`; this method creates a function that executes + * `func` only after it is called `n` times. * * @static * @memberOf _ * @category Function - * @param {number} n The number of times the function must be called before - * `func` is executed. + * @param {number} n The number of calls before `func` is executed. * @param {Function} func The function to restrict. * @returns {Function} Returns the new restricted function. * @example @@ -5475,6 +5474,37 @@ }; } + /** + * Creates a function that executes `func`, with the `this` binding and + * arguments of the created function, until it is called `n` times. + * + * @static + * @memberOf _ + * @category Function + * @param {number} n The number of times `func` may be executed. + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * jQuery('#add').on('click', _.before(5, addContactToList)); + * // => allows adding up to 5 contacts to the list + */ + function before(n, func) { + var result; + if (!isFunction(func)) { + throw new TypeError(funcErrorText); + } + n = nativeIsFinite(n = +n) ? n : 0; + return function() { + if (--n > 0) { + result = func.apply(this, arguments); + } else { + func = null; + } + return result; + }; + } + /** * Creates a function that invokes `func` with the `this` binding of `thisArg` * and prepends any additional `bind` arguments to those provided to the bound @@ -6028,6 +6058,7 @@ * * @static * @memberOf _ + * @type Function * @category Function * @param {Function} func The function to restrict. * @returns {Function} Returns the new restricted function. @@ -6038,25 +6069,7 @@ * initialize(); * // `initialize` executes `createApplication` once */ - function once(func) { - var ran, - result; - - if (!isFunction(func)) { - throw new TypeError(funcErrorText); - } - return function() { - if (ran) { - return result; - } - ran = true; - result = func.apply(this, arguments); - - // clear the `func` variable so the function may be garbage collected - func = null; - return result; - }; - } + var once = partial(before, 2); /** * Creates a function that invokes `func` with any additional `partial` arguments @@ -8888,6 +8901,7 @@ lodash.after = after; lodash.assign = assign; lodash.at = at; + lodash.before = before; lodash.bind = bind; lodash.bindAll = bindAll; lodash.bindKey = bindKey; diff --git a/test/test.js b/test/test.js index 0b55223c0..3d874b34b 100644 --- a/test/test.js +++ b/test/test.js @@ -621,15 +621,29 @@ } test('should create a function that executes `func` after `n` calls', 4, function() { strictEqual(after(5, 5), 1, 'after(n) should execute `func` after being called `n` times'); - strictEqual(after(5, 4), 0, 'after(n) should not execute `func` unless called `n` times'); + strictEqual(after(5, 4), 0, 'after(n) should not execute `func` before being called `n` times'); strictEqual(after(0, 0), 0, 'after(0) should not execute `func` immediately'); strictEqual(after(0, 1), 1, 'after(0) should execute `func` when called once'); }); - test('should coerce non-finite `n` values to `0`', 3, function() { - _.each([-Infinity, NaN, Infinity], function(n) { - strictEqual(after(n, 1), 1); + test('should coerce non-finite `n` values to `0`', 1, function() { + var values = [-Infinity, NaN, Infinity], + expected = _.map(values, _.constant(1)); + + var actual = _.map(values, function(n) { + return after(n, 1); }); + + deepEqual(actual, expected); + }); + + test('should not set a `this` binding', 2, function() { + var after = _.after(1, function() { return ++this.count; }), + object = { 'count': 0, 'after': after }; + + object.after(); + strictEqual(object.after(), 2); + strictEqual(object.count, 2); }); }()); @@ -724,6 +738,44 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.before'); + + (function() { + function before(n, times) { + var count = 0; + _.times(times, _.before(n, function() { count++; })); + return count; + } + test('should create a function that executes `func` after `n` calls', 4, function() { + strictEqual(before(5, 4), 4, 'before(n) should execute `func` before being called `n` times'); + strictEqual(before(5, 6), 4, 'before(n) should not execute `func` after being called `n - 1` times'); + strictEqual(before(0, 0), 0, 'before(0) should not execute `func` immediately'); + strictEqual(before(0, 1), 0, 'before(0) should not execute `func` when called'); + }); + + test('should coerce non-finite `n` values to `0`', 1, function() { + var values = [-Infinity, NaN, Infinity], + expected = _.map(values, _.constant(0)); + + var actual = _.map(values, function(n) { + return before(n); + }); + + deepEqual(actual, expected); + }); + + test('should not set a `this` binding', 2, function() { + var before = _.before(2, function() { return ++this.count; }), + object = { 'count': 0, 'before': before }; + + object.before(); + strictEqual(object.before(), 1); + strictEqual(object.count, 1); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.bind'); (function() { @@ -11316,6 +11368,7 @@ var rejectFalsey = [ 'after', + 'before', 'bind', 'compose', 'curry', @@ -11401,7 +11454,7 @@ }); }); - test('should throw a TypeError for falsey arguments', 16, function() { + test('should throw a TypeError for falsey arguments', 17, function() { _.each(rejectFalsey, function(methodName) { var expected = _.map(falsey, _.constant(true)), func = _[methodName];