Skip to content

Commit 97a54f7

Browse files
committed
Merge pull request #1733 from megawac/partial-constructor
Partial support for constructors
2 parents d761ce7 + 2f89868 commit 97a54f7

File tree

2 files changed

+32
-14
lines changed

2 files changed

+32
-14
lines changed

test/functions.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@
5959

6060
func = _.partial(function() { return typeof arguments[2]; }, _, 'b', _, 'd');
6161
equal(func('a'), 'undefined', 'unfilled placeholders are undefined');
62+
63+
// passes context
64+
function MyWidget(name, options) {
65+
this.name = name;
66+
this.options = options;
67+
}
68+
MyWidget.prototype.get = function() {
69+
return this.name;
70+
};
71+
var MyWidgetWithCoolOpts = _.partial(MyWidget, _, {a: 1});
72+
var widget = new MyWidgetWithCoolOpts('foo');
73+
ok(widget instanceof MyWidget, 'Can partially bind a constructor');
74+
equal(widget.get(), 'foo', 'keeps prototype');
75+
deepEqual(widget.options, {a: 1});
6276
});
6377

6478
test('bindAll', function() {

underscore.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -645,39 +645,43 @@
645645
// Reusable constructor function for prototype setting.
646646
var Ctor = function(){};
647647

648+
// Determines whether to execute a function as a constructor
649+
// or a normal function with the provided arguments
650+
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
651+
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
652+
Ctor.prototype = sourceFunc.prototype;
653+
var self = new Ctor;
654+
Ctor.prototype = null;
655+
var result = sourceFunc.apply(self, args);
656+
if (_.isObject(result)) return result;
657+
return self;
658+
};
659+
648660
// Create a function bound to a given object (assigning `this`, and arguments,
649661
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
650662
// available.
651663
_.bind = function(func, context) {
652-
var args, bound;
653664
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
654-
if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
655-
args = slice.call(arguments, 2);
656-
bound = function() {
657-
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
658-
Ctor.prototype = func.prototype;
659-
var self = new Ctor;
660-
Ctor.prototype = null;
661-
var result = func.apply(self, args.concat(slice.call(arguments)));
662-
if (_.isObject(result)) return result;
663-
return self;
665+
if (!_.isFunction(func)) throw TypeError('Bind must be called on a function');
666+
var args = slice.call(arguments, 2);
667+
return function bound() {
668+
return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
664669
};
665-
return bound;
666670
};
667671

668672
// Partially apply a function by creating a version that has had some of its
669673
// arguments pre-filled, without changing its dynamic `this` context. _ acts
670674
// as a placeholder, allowing any combination of arguments to be pre-filled.
671675
_.partial = function(func) {
672676
var boundArgs = slice.call(arguments, 1);
673-
return function() {
677+
return function bound() {
674678
var position = 0;
675679
var args = boundArgs.slice();
676680
for (var i = 0, length = args.length; i < length; i++) {
677681
if (args[i] === _) args[i] = arguments[position++];
678682
}
679683
while (position < arguments.length) args.push(arguments[position++]);
680-
return func.apply(this, args);
684+
return executeBound(func, bound, this, this, args);
681685
};
682686
};
683687

0 commit comments

Comments
 (0)