为什么Function.prototype.bind变慢?

时间:2011-12-28 12:49:01

标签: javascript google-chrome v8

this benchmark与chrome 16与opera 11.6进行比较时,我们发现

    chrome本地绑定中的
  • 几乎比模拟版本的bind
  • 慢5倍 opera中的
  • 本机绑定几乎是模拟版本的bind
  • 的4倍

在这种情况下,模拟的bind版本是

var emulatebind = function (f, context) {
    return function () {
        f.apply(context, arguments);
    };
};

是否有充分的理由说明存在这样的差异,或者仅仅是v8的问题还不够充分?

注意:emulatebind仅实现了一个子集,但这并不是真正相关的。如果您拥有功能齐全且经过优化的模拟绑定,则performance difference in the benchmark仍然存在。

3 个答案:

答案 0 :(得分:27)

基于http://jsperf.com/bind-vs-emulate/6,它添加了es5-shim版本进行比较,看起来罪魁祸首是绑定版本必须执行的额外分支和instanceof,以测试它是否被调用为构造函数。

每次运行绑定版本时,执行的代码基本上都是:

if (this instanceof bound) {
    // Never reached, but the `instanceof` check and branch presumably has a cost
} else {
    return target.apply(
     that,
     args.concat(slice.call(arguments))
    );

    // args is [] in your case.
    // So the cost is:
    // * Converting (empty) Arguments object to (empty) array.
    // * Concating two empty arrays.
}

In the V8 source code,此检查显示(在boundFunction内)为

if (%_IsConstructCall()) {
    return %NewObjectFromBound(boundFunction);
}

Plaintext link to v8natives.js用于Google代码搜索何时死亡。)

令人费解的是,至少对于Chrome 16来说,es5-shim版本仍然比原生版本更快。并且其他浏览器对于es5-shim与native相比具有相当不同的结果。推测:也许%_IsConstructCall()甚至比this instanceof bound慢,可能是因为跨越了本机/ JS代码边界。也许其他浏览器可以更快地检查[[Construct]]电话。

答案 1 :(得分:7)

仅在ES5中实现功能齐全的bind是不可能的。特别是规范的15.3.4.5.1到15.3.4.5.3部分无法模拟。

特别是,15.3.4.5.1似乎是一个可能的性能负担:在简短的绑定函数中有不同的[[Call]]内部属性,因此调用它们可能会占用一个不寻常的,可能更复杂的代码路径。 / p>

绑定函数的各种其他特定的不可模拟特性(例如arguments / caller中毒,以及可能与原始签名无关的自定义length)可能会增加每个特征的开销打电话,虽然我承认它有点不太可能。虽然看起来V8目前还没有实施中毒。

编辑 这个答案是猜测,但我的其他答案有更接近证据。我仍然认为这是一个有效的推测,但它是一个单独的答案,所以我将保留它,只是引用你到另一个。

答案 2 :(得分:3)

V8 source code for bind在JS中实现。

OP不会模仿bind,因为它不会像bind那样讨论参数。这是一个功能齐全的bind

var emulatebind = function (f, context) {
  var curriedArgs = Array.prototype.slice.call(arguments, 2);
  return function () {
    var allArgs = curriedArgs.slice(0);
    for (var i = 0, n = arguments.length; i < n; ++i) {
      allArgs.push(arguments[i]);
    }
    return f.apply(context, allArgs);
  };
};

显然,快速优化将是

return f.apply(context, arguments);

而不是curriedArgs.length == 0,因为否则你有两个不必要的数组创建和一个不必要的副本,但是本机版本可能真的是在JS中实现的,并没有进行优化。

警告:这个功能齐全的bind在严格模式下无法正确处理this {{1}}个{{1}}个参数强制。这可能是另一个开销来源。