Skip to content

Commit

Permalink
Partial support for constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
gyeates committed Oct 22, 2014
1 parent d761ce7 commit 2f89868
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 14 deletions.
14 changes: 14 additions & 0 deletions test/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@

func = _.partial(function() { return typeof arguments[2]; }, _, 'b', _, 'd');
equal(func('a'), 'undefined', 'unfilled placeholders are undefined');

// passes context
function MyWidget(name, options) {
this.name = name;
this.options = options;
}
MyWidget.prototype.get = function() {
return this.name;
};
var MyWidgetWithCoolOpts = _.partial(MyWidget, _, {a: 1});
var widget = new MyWidgetWithCoolOpts('foo');
ok(widget instanceof MyWidget, 'Can partially bind a constructor');
equal(widget.get(), 'foo', 'keeps prototype');
deepEqual(widget.options, {a: 1});
});

test('bindAll', function() {
Expand Down
32 changes: 18 additions & 14 deletions underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -645,39 +645,43 @@
// Reusable constructor function for prototype setting.
var Ctor = function(){};

// Determines whether to execute a function as a constructor
// or a normal function with the provided arguments
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
Ctor.prototype = sourceFunc.prototype;
var self = new Ctor;
Ctor.prototype = null;
var result = sourceFunc.apply(self, args);
if (_.isObject(result)) return result;
return self;
};

// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
// available.
_.bind = function(func, context) {
var args, bound;
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');
args = slice.call(arguments, 2);
bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
Ctor.prototype = func.prototype;
var self = new Ctor;
Ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (_.isObject(result)) return result;
return self;
if (!_.isFunction(func)) throw TypeError('Bind must be called on a function');
var args = slice.call(arguments, 2);
return function bound() {
return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
};
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, allowing any combination of arguments to be pre-filled.
_.partial = function(func) {
var boundArgs = slice.call(arguments, 1);
return function() {
return function bound() {
var position = 0;
var args = boundArgs.slice();
for (var i = 0, length = args.length; i < length; i++) {
if (args[i] === _) args[i] = arguments[position++];
}
while (position < arguments.length) args.push(arguments[position++]);
return func.apply(this, args);
return executeBound(func, bound, this, this, args);
};
};

Expand Down

0 comments on commit 2f89868

Please sign in to comment.