Chrome 59 の更新: 以下の回答で予測したように、バインドは新しい最適化コンパイラで遅くなりません。詳細を含むコードは次のとおりです: https://codereview.chromium.org/2916063002/
ほとんどの場合、それは問題ではありません。
ボトルネックがどこにあるかアプリケーションを作成していない限り.bind
、私は気にしません。ほとんどの場合、読みやすさはパフォーマンスよりもはるかに重要です。通常、ネイティブを使用すると、より読みやすく保守しやすいコードが提供されると思います.bind
。これは大きなプラスです。
ただし、重要な場合 .bind
は - 遅いです
はい、.bind
クロージャーよりもかなり遅いです-少なくともChromeでは、少なくとも現在の実装方法ではv8
. 私は個人的に、パフォーマンスの問題のために Node.JS に切り替えなければならなかったことがあります (より一般的には、パフォーマンスが集中する状況では、クロージャが遅くなります)。
なんで?アルゴリズムは、関数を別の関数でラップしてor.bind
を使用するよりもはるかに複雑だからです。(おもしろいことに、toString が [native function] に設定された関数も返します)。.call
.apply
これには、仕様の観点からと実装の観点からの 2 つの見方があります。両方観察しましょう。
- Target を this の値とします。
- IsCallable(Target) が false の場合、TypeError 例外をスローします。
- A を、thisArg の後に提供されるすべての引数値 (arg1、arg2 など) の新しい (場合によっては空の) 内部リストとします。
...
(21. F の [[DefineOwnProperty]] 内部メソッドを引数 "arguments"、PropertyDescriptor {[[Get]]: thrower、[[Set]]: thrower、[[Enumerable]]: false、[[Configurable] で呼び出す]: false}、および false。
(22.Fを返す。
ラップだけではなく、かなり複雑に思えます。
FunctionBind
v8 (chrome JavaScript エンジン) のソース コードを確認してみましょう。
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]] 内部プロパティがありません。プロパティ"