Reputation: 27503
A previous poster asked Function.bind vs Closure in Javascript : how to choose?
and received this answer in part, which seems to indicate bind should be faster than a closure:
Scope traversal means, when you are reaching to grab a value (variable,object) that exists in a different scope, therefore additional overhead is added (code becomes slower to execute).
Using bind, you 're calling a function with an existing scope, so that scope traversal does not take place.
Two jsperfs suggest that bind is actually much, much slower than a closure.
This was posted as a comment to the above
And, I decided to write my own jsperf
So why is bind so much slower (70+% on chromium)?
Since it is not faster and closures can serve the same purpose, should bind be avoided?
Upvotes: 85
Views: 23617
Reputation: 1
I just want to give a little bit of perspective here:
Note that while bind()
ing is slow, calling the functions once bound is not!
My test code in Firefox 76.0 on Linux:
//Set it up.
q = function(r, s) {
};
r = {};
s = {};
a = [];
for (let n = 0; n < 1000000; ++n) {
//Tried all 3 of these.
//a.push(q);
//a.push(q.bind(r));
a.push(q.bind(r, s));
}
//Performance-testing.
s = performance.now();
for (let x of a) {
x();
}
e = performance.now();
document.body.innerHTML = (e - s);
So while it is true that .bind()
ing can be some ~2X slower than not binding (I tested that too), the above code takes the same amount of time for all 3 cases (binding 0, 1, or 2 variables).
Personally, I don't care if the .bind()
ing is slow in my current use case, I care about the performance of the code being called once those variables are already bound to the functions.
Upvotes: 6
Reputation: 276596
Chrome 59 update: As I predicted in the answer below bind is no longer slower with the new optimizing compiler. Here's the code with details: https://codereview.chromium.org/2916063002/
Unless you're creating an application where .bind
is the bottleneck I wouldn't bother. Readability is much more important than sheer performance in most cases. I think that using native .bind
usually provides for more readable and maintainable code - which is a big plus.
.bind
is slowerYes, .bind
is considerably slower than a closure - at least in Chrome, at least in the current way it's implemented in v8
. I've personally had to switch in Node.JS for performance issues some times (more generally, closures are kind of slow in performance intensive situations).
Why? Because the .bind
algorithm is a lot more complicated than wrapping a function with another function and using .call
or .apply
. (Fun fact, it also returns a function with toString set to [native function]).
There are two ways to look at this, from the specification point of view, and from the implementation point of view. Let's observe both.
- Let Target be the this value.
- If IsCallable(Target) is false, throw a TypeError exception.
- Let A be a new (possibly empty) internal list of all of the argument values provided after thisArg (arg1, arg2 etc), in order.
...
(21. Call the [[DefineOwnProperty]] internal method of F with arguments "arguments", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, and false.
(22. Return F.
Seems pretty complicated, a lot more than just a wrap.
Let's check FunctionBind
in the v8 (chrome JavaScript engine) source code:
function FunctionBind(this_arg) { // Length is 1.
if (!IS_SPEC_FUNCTION(this)) {
throw new $TypeError('Bind must be called on a function');
}
var boundFunction = function () {
// Poison .arguments and .caller, but is otherwise not detectable.
"use strict";
// This function must not use any object literals (Object, Array, RegExp),
// since the literals-array is being used to store the bound data.
if (%_IsConstructCall()) {
return %NewObjectFromBound(boundFunction);
}
var bindings = %BoundFunctionGetBindings(boundFunction);
var argc = %_ArgumentsLength();
if (argc == 0) {
return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2);
}
if (bindings.length === 2) {
return %Apply(bindings[0], bindings[1], arguments, 0, argc);
}
var bound_argc = bindings.length - 2;
var argv = new InternalArray(bound_argc + argc);
for (var i = 0; i < bound_argc; i++) {
argv[i] = bindings[i + 2];
}
for (var j = 0; j < argc; j++) {
argv[i++] = %_Arguments(j);
}
return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
};
%FunctionRemovePrototype(boundFunction);
var new_length = 0;
if (%_ClassOf(this) == "Function") {
// Function or FunctionProxy.
var old_length = this.length;
// FunctionProxies might provide a non-UInt32 value. If so, ignore it.
if ((typeof old_length === "number") &&
((old_length >>> 0) === old_length)) {
var argc = %_ArgumentsLength();
if (argc > 0) argc--; // Don't count the thisArg as parameter.
new_length = old_length - argc;
if (new_length < 0) new_length = 0;
}
}
// This runtime function finds any remaining arguments on the stack,
// so we don't pass the arguments object.
var result = %FunctionBindArguments(boundFunction, this,
this_arg, new_length);
// We already have caller and arguments properties on functions,
// which are non-configurable. It therefore makes no sence to
// try to redefine these as defined by the spec. The spec says
// that bind should make these throw a TypeError if get or set
// is called and make them non-enumerable and non-configurable.
// To be consistent with our normal functions we leave this as it is.
// TODO(lrn): Do set these to be thrower.
return result;
We can see a bunch of expensive things here in the implementation. Namely %_IsConstructCall()
. This is of course needed to abide to the specification - but it also makes it slower than a simple wrap in many cases.
On another note, calling .bind
is also slightly different, the spec notes "Function objects created using Function.prototype.bind do not have a prototype property or the [[Code]], [[FormalParameters]], and [[Scope]] internal properties"
Upvotes: 151