首页 文章

为什么绑定比闭包慢?

提问于
浏览
75

之前的一张海报问Function.bind vs Closure in Javascript : how to choose?

并且部分收到了这个答案,这似乎表明bind应该比闭包更快:

范围遍历意味着,当您要获取存在于不同范围内的值(变量,对象)时,会增加额外开销(代码执行速度变慢) . 使用bind,您将调用具有现有范围的函数,因此不会进行范围遍历 .

两个jsperfs表明bind实际上比closure慢得多 .

这是作为对上述评论发布的

而且,我决定写my own jsperf

那么为什么绑定这么慢(铬含量为70%)?

由于它不是更快并且闭包可以起到相同的作用,应该避免绑定吗?

1 回答

  • 135

    Chrome 59更新:正如我在下面的答案中预测的那样,使用新的优化编译器时,bind不再慢 . 这是包含详细信息的代码:https://codereview.chromium.org/2916063002/

    大多数时候没关系 .

    除非您正在创建一个 .bind 是瓶颈的应用程序,否则我不会打扰 . 在大多数情况下,可读性比纯粹的性能重要得多 . 我认为使用原生 .bind 通常提供更易读和可维护的代码 - 这是一个很大的优点 .

    但是,重要的是--.bind比较慢

    是的, .bind 比关闭要慢得多 - 至少在Chrome中是这样,至少在 v8 中实现的当前方式 . 我个人不得不在某些时候切换Node.JS以解决性能问题(更一般地说,在性能密集的情况下,闭包有点慢) .

    为什么?因为 .bind 算法比用另一个函数包装函数并使用 .call.apply 复杂得多 . (有趣的是,它还返回一个函数,其中toString设置为[native function]) .

    从规范的角度和实现的角度来看,有两种方法可以看待这种情况 . 让我们观察两者 .

    首先,让我们看一下规范中定义的绑定算法:

    让Target成为这个值 . 如果IsCallable(Target)为false,则抛出TypeError异常 . 设A是按此顺序在thisArg(arg1,arg2等)之后提供的所有参数值的新(可能为空)内部列表 . ...(21.使用参数“arguments”调用F的[[DefineOwnProperty]]内部方法,PropertyDescriptor {[[Get]]:thrower,[[Set]]:thrower,[[Enumerable]]:false,[ [Configurable]]:false},false . (22 . 返回F.

    看起来很复杂,不仅仅是一个包装 .

    其次,让我们看看它是如何在Chrome中实现的 .

    让我们检查v8(chrome JavaScript引擎)源代码中的FunctionBind

    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;
    

    我们可以在实现中看到一堆昂贵的东西 . 即 %_IsConstructCall() . 这当然需要遵守规范 - 但在许多情况下它也比简单的包装慢 .


    另一方面,调用 .bind 也略有不同,规格注释"Function objects created using Function.prototype.bind do not have a prototype property or the [[Code]], [[FormalParameters]], and [[Scope]] internal properties"

相关问题