综合技术

Why is bind slower than a closure?

微信扫一扫,分享到朋友圈

Why is bind slower than a closure?
0

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?

Problem courtesy of: Paul

Solution

Most of the time it does not matter.

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.

However yes, when it matters – .bind
is slower

Yes, .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.

First, let’s look at the bind algorithm defined in the specification
:

  1. Let Target be the this value.
  2. If IsCallable(Target) is false, throw a TypeError exception.
  3. 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.

Second , let’s see how it’s implemented in Chrome
.

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"

Solution courtesy of: Benjamin Gruenbaum

Discussion

There is currently no discussion for this recipe.

This recipe can be found in it’s original form on Stack Over Flow
.

阅读原文...


微信扫一扫,分享到朋友圈

Why is bind slower than a closure?
0

Node.js Recipes

母亲总把最好的留给你,但你可能不知道准备得有多早

上一篇

Developers’ Take: Tech Roles are in High Demand in India and Brazil

下一篇

评论已经被关闭。

插入图片

热门分类

往期推荐

Why is bind slower than a closure?

长按储存图像,分享给朋友