上一张海报问Function.bind vs Closure in Javascript : how to choose?
并且部分收到了这个答案,这似乎表明bind应该比闭包更快:
范围遍历意味着,当您达到获取值时 因此,(变量,对象)存在于不同的范围内 添加了额外的开销(代码执行起来变慢)。
使用bind,您将调用具有现有范围的函数,以便 范围遍历不会发生。
两个jsperfs表明bind实际上比closure慢得多。
这是作为对上述
的评论发布的而且,我决定写my own jsperf
那么为什么绑定这么慢(铬+上70 +%)?
由于它不是更快并且闭包可以起到相同的作用,应该避免绑定吗?
答案 0 :(得分:135)
Chrome 59更新:正如我在下面的答案中预测的那样,使用新的优化编译器时,bind不再慢。以下是包含详细信息的代码:https://codereview.chromium.org/2916063002/
除非您创建的应用程序中.bind
是瓶颈,否则我不会打扰。在大多数情况下,可读性比纯粹的性能重要得多。我认为使用原生.bind
通常会提供更易读和可维护的代码 - 这是一个很大的优势。
.bind
更慢是的,.bind
比关闭要慢得多 - 至少在Chrome中是这样,至少在v8
中实现的方式是这样。我个人不得不在某些时候切换Node.JS以解决性能问题(更一般地说,在性能密集的情况下,闭包有点慢)。
为什么呢?因为.bind
算法比使用其他函数包装函数并使用.call
或.apply
复杂得多。 (有趣的是,它还返回一个函数,其中toString设置为[native function])。
从规范的角度和实现的角度来看,有两种方法可以解决这个问题。让我们观察两者。
- 让Target为此值。
- 如果IsCallable(Target)为false,则抛出TypeError异常。
- 设A是按此顺序在thisArg(arg1,arg2等)之后提供的所有参数值的新(可能为空)内部列表。
醇>...
(21。使用参数“arguments”调用F的[[DefineOwnProperty]]内部方法,PropertyDescriptor {[[Get]]:thrower,[[Set]]:thrower,[[Enumerable]]:false,[ [Configurable]]:false},false。
(22。返回F.
看起来很复杂,不仅仅是一个包装。
让我们检查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]],[[FormalParameters]] ,和[[Scope]]内部属性“
答案 1 :(得分:1)
我只想在这里提供一些观点:
请注意,虽然 bind()
的运行速度很慢,但调用一次绑定的功能却并非如此!
我在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()
的确比不绑定慢了约2倍(我也测试过),但是上述代码在所有3种情况下都花费了相同的时间(绑定0、1,或2个变量)。
就我个人而言,我并不在意当前的用例中.bind()
的执行速度是否很慢,我关心的是一旦这些变量已经绑定到函数上,代码的调用性能。