Skip to content

Commit d269b17

Browse files
committed
Merge pull request #2063 from jridgewell/ie8-bound-functions
Fixes bound functions in IE8
2 parents 9479bc4 + 19405ca commit d269b17

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

test/functions.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,22 @@
2727
func = function(salutation, firstname, lastname) { return salutation + ': ' + firstname + ' ' + lastname; };
2828
func = _.bind(func, this, 'hello', 'moe', 'curly');
2929
equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments');
30-
30+
31+
func = function(context, message) { equal(this, context, message); };
32+
_.bind(func, 0, 0, 'can bind a function to `0`')();
33+
_.bind(func, '', '', 'can bind a function to an empty string')();
34+
_.bind(func, false, false, 'can bind a function to `false`')();
35+
36+
// These tests are only meaningful when using a browser without a native bind function
37+
// To test this with a modern browser, set underscore's nativeBind to undefined
38+
var F = function () { return this; };
39+
var boundf = _.bind(F, {hello: 'moe curly'});
40+
var Boundf = boundf; // make eslint happy.
41+
var newBoundf = new Boundf();
42+
equal(newBoundf.hello, undefined, 'function should not be bound to the context, to comply with ECMAScript 5');
43+
equal(boundf().hello, 'moe curly', "When called without the new operator, it's OK to be bound to the context");
44+
ok(newBoundf instanceof F, 'a bound instance is an instance of the original function');
45+
3146
throws(function() { _.bind('notafunction'); }, TypeError, 'throws an error when binding to a non-function');
3247
});
3348

@@ -47,6 +62,20 @@
4762

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

5281
test('bindAll', function() {

underscore.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@
108108
};
109109
};
110110

111+
// An internal function for creating a new object that inherits from another.
112+
var baseCreate = function(prototype) {
113+
if (!_.isObject(prototype)) return {};
114+
if (nativeCreate) return nativeCreate(prototype);
115+
Ctor.prototype = prototype;
116+
var result = new Ctor;
117+
Ctor.prototype = null;
118+
return result;
119+
};
120+
111121
// Helper for collection methods to determine whether a collection
112122
// should be iterated as an array or as an object
113123
// Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
@@ -674,32 +684,44 @@
674684
// Function (ahem) Functions
675685
// ------------------
676686

687+
// Determines whether to execute a function as a constructor
688+
// or a normal function with the provided arguments
689+
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
690+
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
691+
var self = baseCreate(sourceFunc.prototype);
692+
var result = sourceFunc.apply(self, args);
693+
if (_.isObject(result)) return result;
694+
return self;
695+
};
696+
677697
// Create a function bound to a given object (assigning `this`, and arguments,
678698
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
679699
// available.
680700
_.bind = function(func, context) {
681701
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
682702
if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
683703
var args = slice.call(arguments, 2);
684-
return function bound() {
685-
return func.apply(context || this, args.concat(slice.call(arguments)));
704+
var bound = function() {
705+
return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
686706
};
707+
return bound;
687708
};
688709

689710
// Partially apply a function by creating a version that has had some of its
690711
// arguments pre-filled, without changing its dynamic `this` context. _ acts
691712
// as a placeholder, allowing any combination of arguments to be pre-filled.
692713
_.partial = function(func) {
693714
var boundArgs = slice.call(arguments, 1);
694-
return function bound() {
715+
var bound = function() {
695716
var position = 0, length = boundArgs.length;
696717
var args = Array(length);
697718
for (var i = 0; i < length; i++) {
698719
args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
699720
}
700721
while (position < arguments.length) args.push(arguments[position++]);
701-
return func.apply(this, args);
722+
return executeBound(func, bound, this, this, args);
702723
};
724+
return bound;
703725
};
704726

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

0 commit comments

Comments
 (0)