将this benchmark与chrome 16与opera 11.6进行比较时,我们发现
在这种情况下,模拟的bind版本是
var emulatebind = function (f, context) {
return function () {
f.apply(context, arguments);
};
};
是否有充分的理由说明存在这样的差异,或者仅仅是v8的问题还不够充分?
注意:emulatebind
仅实现了一个子集,但这并不是真正相关的。如果您拥有功能齐全且经过优化的模拟绑定,则performance difference in the benchmark仍然存在。
答案 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部分无法模拟。
[[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}}个参数强制。这可能是另一个开销来源。