Skip to content

Commit

Permalink
Merge pull request #2140 from jashkenas/restParams
Browse files Browse the repository at this point in the history
Add _.restParam
  • Loading branch information
jashkenas committed Apr 9, 2015
2 parents 0a9d9d8 + 3caa2b4 commit 4f2474c
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 29 deletions.
28 changes: 28 additions & 0 deletions test/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,4 +588,32 @@

});

test('restArgs', 10, function() {
_.restArgs(function(a, args) {
strictEqual(a, 1);
deepEqual(args, [2, 3], 'collects rest arguments into an array');
})(1, 2, 3);

_.restArgs(function(a, args) {
strictEqual(a, undefined);
deepEqual(args, [], 'passes empty array if there are not enough arguments');
})();

_.restArgs(function(a, b, c, args) {
strictEqual(arguments.length, 4);
deepEqual(args, [4, 5], 'works on functions with many named parameters');
})(1, 2, 3, 4, 5);

var obj = {};
_.restArgs(function() {
strictEqual(this, obj, 'invokes function with this context');
}).call(obj);

_.restArgs(function(array, iteratee, context) {
deepEqual(array, [1, 2, 3, 4], 'startIndex can be used manually specify index of rest parameter');
strictEqual(iteratee, undefined);
strictEqual(context, undefined);
}, 0)(1, 2, 3, 4);
});

}());
76 changes: 47 additions & 29 deletions underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

// Create quick reference variables for speed access to core prototypes.
var
push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
Expand Down Expand Up @@ -94,6 +93,30 @@
return cb(value, context, Infinity);
};

// Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html)
// This accumulates the arguments passed into an array, after a given index.
var restArgs = function(func, startIndex) {
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
var length = Math.max(arguments.length - startIndex, 0);
var rest = Array(length);
for (var index = 0; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
switch (startIndex) {
case 0: return func.call(this, rest);
case 1: return func.call(this, arguments[0], rest);
case 2: return func.call(this, arguments[0], arguments[1], rest);
}
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
};

// An internal function for creating a new object that inherits from another.
var baseCreate = function(prototype) {
if (!_.isObject(prototype)) return {};
Expand Down Expand Up @@ -251,14 +274,13 @@
};

// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
_.invoke = restArgs(function(obj, method, args) {
var isFunc = _.isFunction(method);
return _.map(obj, function(value) {
var func = isFunc ? method : value[method];
return func == null ? func : func.apply(value, args);
});
};
});

// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
Expand Down Expand Up @@ -489,9 +511,9 @@
};

// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
_.without = restArgs(function(array, otherArrays) {
return _.difference(array, otherArrays);
});

// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
Expand Down Expand Up @@ -689,19 +711,18 @@
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
var args = slice.call(arguments, 2);
var bound = function() {
return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
};
var bound = restArgs(function(callArgs) {
return executeBound(func, bound, context, this, args.concat(callArgs));
});
return bound;
};

// Partially apply a function by creating a version that has had some of its
// arguments pre-filled, without changing its dynamic `this` context. _ acts
// as a placeholder by default, allowing any combination of arguments to be
// pre-filled. Set `_.partial.placeholder` for a custom placeholder argument.
_.partial = function(func) {
var boundArgs = slice.call(arguments, 1),
placeholder = _.partial.placeholder;
_.partial = restArgs(function(func, boundArgs) {
var placeholder = _.partial.placeholder;
var bound = function() {
var position = 0, length = boundArgs.length;
var args = Array(length);
Expand All @@ -712,22 +733,19 @@
return executeBound(func, bound, this, this, args);
};
return bound;
};
});

_.partial.placeholder = _;

// Bind a number of an object's methods to that object. Remaining arguments
// are the method names to be bound. Useful for ensuring that all callbacks
// defined on an object belong to it.
_.bindAll = function(obj) {
var i, length = arguments.length, key;
if (length <= 1) throw new Error('bindAll must be passed function names');
for (i = 1; i < length; i++) {
key = arguments[i];
_.bindAll = restArgs(function(obj, keys) {
if (keys.length < 1) throw new Error('bindAll must be passed function names');
return _.each(keys, function(key) {
obj[key] = _.bind(obj[key], obj);
}
return obj;
};
});
});

// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
Expand All @@ -743,12 +761,11 @@

// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
_.delay = restArgs(function(func, wait, args) {
return setTimeout(function(){
return func.apply(null, args);
}, wait);
};
});

// Defers a function, scheduling it to run after the current call stack has
// cleared.
Expand Down Expand Up @@ -879,6 +896,8 @@
// often you call it. Useful for lazy initialization.
_.once = _.partial(_.before, 2);

_.restArgs = restArgs;

// Object Functions
// ----------------

Expand Down Expand Up @@ -1489,11 +1508,10 @@
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
_.prototype[name] = restArgs(function(args) {
args.unshift(this._wrapped);
return result(this, func.apply(_, args));
};
});
});
};

Expand Down

0 comments on commit 4f2474c

Please sign in to comment.