为什么绑定比闭包慢?

以前的海报在Javascript中使用Function.bind vs Closure问题:如何select?

并收到了这个答案的一部分,这似乎表明绑定应该比closures更快:

范围遍历意味着,当您要获取存在于不同范围内的值(variables,对象)时,因此会增加额外开销(代码执行速度会变慢)。

使用绑定,你正在调用一个现有的范围的函数,所以范围遍历不会发生。

两个jsperfs表明绑定实际上比闭包慢得多。

这是作为对上述评论发布

而且,我决定写我自己的jsperf

那么为什么要这么慢(铬含量70%以上)呢?

既然速度不快,closures可以达到同样的目的,应该避免绑定?

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

大部分时间没关系。

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

不过是的,什么时候重要 – .bind比较慢

是的, .bind比闭包慢得多 – 至less在Chrome中,至less目前它是在v8实现的。 我个人不得不在Node.JS中切换一些性能问题(更一般的情况是,在性能密集的情况下,闭包是缓慢的)。

为什么? 因为.bindalgorithm比用另一个函数包装函数并使用.apply.apply要复杂得多。 (有趣的是,它也返回一个函数toString设置为[本地函数])。

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

首先,我们来看看规范中定义的绑定algorithm :

  1. 让Target成为这个值。
  2. 如果IsCallable(Target)为false,则引发TypeErrorexception。
  3. 假设A是一个新的(可能是空的)内部列表,这个依次在thisArg(arg1,arg2等)之后提供的所有参数值。

(21.调用参数“arguments”,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.prototype.bind创build的函数对象没有原型属性或[[Code]],[[FormalParameters]]和[[Scope] ]内部属性“