Make _.bind follow ES5 spec so it will work with a common Backbone pattern. [closes #11]

Former-commit-id: 8d5e399ca9727a32604601f81fffd9134104c8f4
This commit is contained in:
John-David Dalton
2012-05-24 01:25:08 -04:00
parent baa37450cc
commit c62b24b024
2 changed files with 90 additions and 9 deletions

View File

@@ -472,6 +472,15 @@
return '\\' + escapes[match]; return '\\' + escapes[match];
} }
/**
* A no-operation function.
*
* @private
*/
function noop() {
// no operation performed
}
/** /**
* Used by `template()` to replace "escape" template delimiters with tokens. * Used by `template()` to replace "escape" template delimiters with tokens.
* *
@@ -1701,18 +1710,20 @@
} }
// use if `Function#bind` is faster // use if `Function#bind` is faster
else if (nativeBind) { else if (nativeBind) {
func = nativeBind.call.apply(nativeBind, arguments); return nativeBind.call.apply(nativeBind, arguments);
return function() { }
return arguments.length ? func.apply(undefined, arguments) : func(); // spec'd to throw a TypeError
}; // http://es5.github.com/#x15.3.4.5
else if (toString.call(func) != funcClass) {
throw new TypeError;
} }
var partialArgs = slice.call(arguments, 2), var partialArgs = slice.call(arguments, 2),
partialArgsLength = partialArgs.length; partialArgsLength = partialArgs.length;
return function() { function bound() {
var result, var args = arguments,
args = arguments; thisBinding = thisArg;
if (!isFunc) { if (!isFunc) {
func = thisArg[methodName]; func = thisArg[methodName];
@@ -1724,10 +1735,27 @@
} }
args = partialArgs; args = partialArgs;
} }
result = args.length ? func.apply(thisArg, args) : func.call(thisArg); var isInstance = this instanceof bound;
if (isInstance) {
// get `func` instance if `bound` is invoked in a `new` expression
noop.prototype = func.prototype;
thisBinding = new noop;
}
var result = args.length ? func.apply(thisBinding, args) : func.call(thisBinding);
partialArgs.length = partialArgsLength; partialArgs.length = partialArgsLength;
if (isInstance) {
// mimic a constructor's `return` behavior
// http://es5.github.com/#x13.2.2
return objectTypes[typeof result] && result !== null
? result
: thisBinding
}
return result; return result;
}; }
return bound;
} }
/** /**

View File

@@ -55,6 +55,17 @@
'seventeen', 'eighteen', 'nineteen', 'twenty' 'seventeen', 'eighteen', 'nineteen', 'twenty'
]; ];
var ctor = function() { },
func = function(greeting) { return greeting + ': ' + this.name; };
var lodashBoundNormal = lodash.bind(func, { 'name': 'moe' }),
lodashBoundCtor = lodash.bind(ctor, { 'name': 'moe' }),
lodashBoundPartial = lodash.bind(func, { 'name': 'moe' }, 'hi');
var _boundNormal = _.bind(func, { 'name': 'moe' }),
_boundCtor = _.bind(ctor, { 'name': 'moe' }),
_boundPartial = _.bind(func, { 'name': 'moe' }, 'hi');
for (var index = 0; index < 20; index++) { for (var index = 0; index < 20; index++) {
numbers[index] = index; numbers[index] = index;
object['key' + index] = index; object['key' + index] = index;
@@ -108,6 +119,48 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('bind call')
.add('Lo-Dash', function() {
lodash.bind(func, { 'name': 'moe' }, 'hi');
})
.add('Underscore', function() {
_.bind(func, { 'name': 'moe' }, 'hi');
})
);
suites.push(
Benchmark.Suite('bound normal')
.add('Lo-Dash', function() {
lodashBoundNormal();
})
.add('Underscore', function() {
_boundNormal();
})
);
suites.push(
Benchmark.Suite('bound partial')
.add('Lo-Dash', function() {
lodashBoundPartial();
})
.add('Underscore', function() {
_boundPartial();
})
);
suites.push(
Benchmark.Suite('bound constructor')
.add('Lo-Dash', function() {
new lodashBoundCtor();
})
.add('Underscore', function() {
new _boundCtor();
})
);
/*--------------------------------------------------------------------------*/
suites.push( suites.push(
Benchmark.Suite('each array') Benchmark.Suite('each array')
.add('Lo-Dash', function() { .add('Lo-Dash', function() {