From 2f8986806ba6ca4a2ed570cc5b7200c1d3343c4b Mon Sep 17 00:00:00 2001 From: Graeme Yeates Date: Thu, 10 Jul 2014 13:19:48 -0400 Subject: [PATCH] Partial support for constructors --- test/functions.js | 14 ++++++++++++++ underscore.js | 32 ++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/test/functions.js b/test/functions.js index abdbc8c53..bbe4abbfe 100644 --- a/test/functions.js +++ b/test/functions.js @@ -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() { diff --git a/underscore.js b/underscore.js index 6371899b4..8830a470e 100644 --- a/underscore.js +++ b/underscore.js @@ -645,24 +645,28 @@ // 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 @@ -670,14 +674,14 @@ // 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); }; };