From 00cd88cfa64cb81fac1a383842c172b7c180d783 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 19 Oct 2011 16:51:53 -0400 Subject: [PATCH] Enhancing _.bind() to support binding constructors ... which you would never, ever want to do. --- test/functions.js | 7 +++++++ underscore.js | 18 ++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/test/functions.js b/test/functions.js index af35e5eff..05155eca9 100644 --- a/test/functions.js +++ b/test/functions.js @@ -29,6 +29,13 @@ $(document).ready(function() { _.bind(func, 0, 0, 'can bind a function to `0`')(); _.bind(func, '', '', 'can bind a function to an empty string')(); _.bind(func, false, false, 'can bind a function to `false`')(); + + // These tests are only meaningful when using a browser without a native bind function + // To test this with a modern browser, set underscore's nativeBind to undefined + var F = function () { return this; }; + var Boundf = _.bind(F, {hello: "moe curly"}); + equal(new Boundf().hello, undefined, "function should not be bound to the context, to comply with ECMAScript 5"); + equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context"); }); test("functions: bindAll", function() { diff --git a/underscore.js b/underscore.js index 55b72818d..a50ac4b81 100644 --- a/underscore.js +++ b/underscore.js @@ -464,15 +464,25 @@ // Function (ahem) Functions // ------------------ + // Reusable constructor function for prototype setting. + var ctor = function(){}; + // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Binding with arguments is also known as `curry`. // Delegates to **ECMAScript 5**'s native `Function.bind` if available. // We check for `func.bind` first, to fail fast when `func` is undefined. - _.bind = function(func, obj) { + _.bind = function bind(func, context) { + var bound, args; if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - var args = slice.call(arguments, 2); - return function() { - return func.apply(obj, args.concat(slice.call(arguments))); + if (!_.isFunction(func)) throw new TypeError; + args = slice.call(arguments, 2); + return bound = function() { + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); + ctor.prototype = func.prototype; + var self = new ctor; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) return result; + return self; }; };