为什么绑定比闭包慢?

之前的一个海报问 Bind vs Closure in Javascript: 如何选择?

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

范围遍历意味着,当您要到达某个值时 (变量,对象)存在于不同的作用域,因此 增加了额外的开销(代码执行起来变慢了)。

通过使用 bind,可以调用具有现有作用域的函数,以便 范围遍历不会发生。

两个 jspers 表明 bind 实际上比 了结慢得多。

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

然后,我决定写 我自己的 jsperf

那么,为什么绑定这么慢(70% 以上的铬) ?

因为它并不快,而闭包可以起到同样的作用,所以应该避免绑定吗?

21354 次浏览

Chrome59更新: 正如我在下面的答案中所预测的那样,新编译器最佳化的绑定速度不再慢了。下面是带有详细信息的代码: https://codereview.chromium.org/2916063002/

大多数时候这并不重要。

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

然而,是的,当它的问题-.bind是慢

是的,.bind比关闭要慢得多——至少在 Chrome 中是这样,至少目前在 v8中是这样实现的。我个人有时不得不切换 Node.JS 来解决性能问题(更一般地说,闭包在性能密集型情况下会比较慢)。

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

从规范的角度和实现的角度来看,有两种方法来看待这个问题。我们两个都观察一下。

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

  1. 让 Target 为此值。
  2. 如果 IsCallable (Target)为 false,则引发 TypeError 异常。
  3. 设 A 是 this Arg (arg1、 arg2等)之后提供的所有参数值的一个新的(可能是空的)内部列表,按顺序排列。

...

(21.使用参数“参数”调用 F 的[ Definition eOwnProperty ]]内部方法,PropertyDescriptor {[[[ Get ]] : thrower,[[ Set ]] : thrower,[[枚举]] : false,[[可配置]] : 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 创建的函数对象没有原型属性或[[ Code ]]、[[ FormalParameter ]和[[ Scope ]]内部属性”

我只是想提供一些观点:

请注意,虽然 bind()ing是慢的,打来的的函数一旦绑定是不!

我在 Linux 上 Firefox 76.0的测试代码:

//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);

因此,虽然 .bind()ing 确实可以比不绑定慢约2倍(我也测试过) ,但是上面的代码对于所有3种情况(绑定0、1或2个变量)花费的时间是相同的。


就个人而言,我不在乎 .bind()ing 在我当前的用例中是否缓慢,我在乎的是一旦那些变量已经绑定到函数之后被调用的代码的性能。