Skip to content

Commit

Permalink
Merge pull request jashkenas#2063 from jridgewell/ie8-bound-functions
Browse files Browse the repository at this point in the history
Fixes bound functions in IE8
  • Loading branch information
jashkenas committed Feb 20, 2015
2 parents 9479bc4 + 19405ca commit d269b17
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 5 deletions.
31 changes: 30 additions & 1 deletion test/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,22 @@
func = function(salutation, firstname, lastname) { return salutation + ': ' + firstname + ' ' + lastname; };
func = _.bind(func, this, 'hello', 'moe', 'curly');
equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments');


func = function(context, message) { equal(this, context, message); };
_.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'});
var Boundf = boundf; // make eslint happy.
var newBoundf = new Boundf();
equal(newBoundf.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");
ok(newBoundf instanceof F, 'a bound instance is an instance of the original function');

throws(function() { _.bind('notafunction'); }, TypeError, 'throws an error when binding to a non-function');
});

Expand All @@ -47,6 +62,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
30 changes: 26 additions & 4 deletions underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@
};
};

// An internal function for creating a new object that inherits from another.
var baseCreate = function(prototype) {
if (!_.isObject(prototype)) return {};
if (nativeCreate) return nativeCreate(prototype);
Ctor.prototype = prototype;
var result = new Ctor;
Ctor.prototype = null;
return result;
};

// Helper for collection methods to determine whether a collection
// should be iterated as an array or as an object
// Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
Expand Down Expand Up @@ -674,32 +684,44 @@
// Function (ahem) Functions
// ------------------

// 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);
var self = baseCreate(sourceFunc.prototype);
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) {
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);
return function bound() {
return func.apply(context || this, args.concat(slice.call(arguments)));
var bound = function() {
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 bound() {
var bound = function() {
var position = 0, length = boundArgs.length;
var args = Array(length);
for (var i = 0; i < length; i++) {
args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
}
while (position < arguments.length) args.push(arguments[position++]);
return func.apply(this, args);
return executeBound(func, bound, this, this, args);
};
return bound;
};

// Bind a number of an object's methods to that object. Remaining arguments
Expand Down

0 comments on commit d269b17

Please sign in to comment.