80

以前の投稿者は、Function.bind vs Closure in Javascript : how to choose? を尋ねました。

この回答を部分的に受け取りました。これは、バインドがクロージャーよりも高速であることを示しているようです:

スコープトラバーサルとは、別のスコープに存在する値 (変数、オブジェクト) を取得しようとすると、追加のオーバーヘッドが追加されることを意味します (コードの実行が遅くなります)。

bind を使用すると、既存のスコープで関数を呼び出しているため、スコープのトラバーサルは行われません。

2 つの jsperfs は、バインドが実際にはクロージャーよりもはるかに遅いことを示唆しています。

これは上記へのコメントとして投稿されました

そして、私は自分のjsperfを書くことにしました

では、バインドが非常に遅いのはなぜですか (クロムで 70+%)?

高速ではなく、クロージャーが同じ目的を果たすことができるため、バインドを避ける必要がありますか?

4

2 に答える 2

146

Chrome 59 の更新: 以下の回答で予測したように、バインドは新しい最適化コンパイラで遅くなりません。詳細を含むコードは次のとおりです: https://codereview.chromium.org/2916063002/

ほとんどの場合、それは問題ではありません。

ボトルネックがどこにあるかアプリケーションを作成していない限り.bind、私は気にしません。ほとんどの場合、読みやすさはパフォーマンスよりもはるかに重要です。通常、ネイティブを使用すると、より読みやすく保守しやすいコードが提供されると思います.bind。これは大きなプラスです。

ただし、重要な場合 .bindは - 遅いです

はい、.bindクロージャーよりもかなり遅いです-少なくともChromeでは、少なくとも現在の実装方法ではv8. 私は個人的に、パフォーマンスの問題のために Node.JS に切り替えなければならなかったことがあります (より一般的には、パフォーマンスが集中する状況では、クロージャが遅くなります)。

なんで?アルゴリズムは、関数を別の関数でラップしてor.bindを使用するよりもはるかに複雑だからです。(おもしろいことに、toString が [native function] に設定された関数も返します)。.call.apply

これには、仕様の観点からと実装の観点からの 2 つの見方があります。両方観察しましょう。

まず、仕様で定義されているバインド アルゴリズムを見てみましょう。

  1. Target を this の値とします。
  2. IsCallable(Target) が false の場合、TypeError 例外をスローします。
  3. A を、thisArg の後に提供されるすべての引数値 (arg1、arg2 など) の新しい (場合によっては空の) 内部リストとします。

...

(21. F の [[DefineOwnProperty]] 内部メソッドを引数 "arguments"、PropertyDescriptor {[[Get]]: thrower、[[Set]]: thrower、[[Enumerable]]: false、[[Configurable] で呼び出す]: false}、および false。

(22.Fを返す。

ラップだけではなく、かなり複雑に思えます。

次に、 Chrome での実装方法を見てみましょう。

FunctionBindv8 (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]] 内部プロパティがありません。プロパティ"

于 2013-07-14T10:25:00.200 に答える