2013-07-14 83 views
70

一个以前的海报问Function.bind vs Closure in Javascript : how to choose?为什么绑定比闭包慢?

并获得这个答案部分,这似乎表明绑定应该比一个封闭速度快:

范围遍历意味着,当你伸手抓住一个值 (变量,对象),因此 增加了额外开销(代码执行速度变慢)。

使用绑定,您正在调用具有现有作用域的函数,以便不发生范围遍历。

两个jsperfs表明绑定实际上比很多,比closure慢得多。

这被张贴上述

评论而且,我决定写my own jsperf

那么,为什么这么绑定慢得多(70 +%的铬)?

既然它不是更快,关闭可以达到同样的目的,应该避免绑定?

+10

”应该避免绑定“---除非您每千页时间做一页 - 您不应该关心它。 – zerkms

+0

从小块中组装异步复杂任务可能需要与nodejs完全相同的东西,因为回调需要以某种方式对齐。 – Paul

+0

我想这是因为浏览器没有尽最大努力来优化它。请参阅Mozilla的代码(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility)以手动实现它。浏览器只是在内部做这些事情,这比快速关闭更有效。 – Dave

回答

127

Chrome 59更新:正如我在下面的答案中预测的那样,使用新的优化编译器,bind不再会变慢。以下是具体细节的代码:https://codereview.chromium.org/2916063002/

大多数情况下并不重要。

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

但是,是的,当它的事项 - .bind较慢

是,.bind相当比一个封闭慢 - 至少在Chrome中,至少它在v8实施的电流的方式。我个人不得不在Node.JS中多次切换性能问题(更一般的情况是,在性能密集的情况下,闭包速度很慢)。

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

从规范的角度以及从实现的角度来看,有两种方法来看待这个问题。我们来观察两者。

首先,让我们look at the bind algorithm defined in the specification

  1. 我们的目标是这个值。
  2. 如果IsCallable(Target)为false,则引发TypeError异常。
  3. 设A是按顺序在thisArg(arg1,arg2等)之后提供的所有参数值的新(可能为空)内部列表。

...

(21.调用F与论据 “论据” 的[[DefineOwnProperty]]内部方法,PropertyDescriptor的{[[获取]:运动员,[设置]:运动员,[可枚举]:假的,[配置]:。假},假

(22返回F.

似乎很复杂,不仅仅是一个包装多很多

秒ond,让我们看看how it's implemented in Chrome

让我们来看看FunctionBind在V8(Chrome的JavaScript引擎)的源代码:

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.prototype.bind创建的规范说明“函数对象没有prototype属性或[编号],[FormalParameters] ,[[Scope]]内部属性“

+0

如果f = g.bind(stuff);应该f()比g(stuff)慢吗?我可以很快地发现这一点,我只是好奇,如果每次我们调用一个函数时都会发生同样的事情,不管这个函数实例化了什么,或者它依赖于函数的来源。 – Paul

+4

@Paul以一些怀疑态度回答我的回答。所有这些都可能在未来版本的Chrome(/ V8)中进行优化。我很少发现自己在浏览器中避免使用'.bind',在大多数情况下,可读和可理解的代码更重要。至于绑定函数的速度 - [是的,绑定函数现在会保持较慢](http://jsperf.com/bind-vs-native-bind-run),特别是当'this'值不用于部分。你可以从基准,从规范和/或独立实现[(基准)](http://jsperf.com/bind-vs-native-bind-run)中看到这一点。 –

+4

感谢您的详细解答。它超出了预期。 – Paul